1
0
Fork 0
timelinize/datasources/smsbackuprestore/sms.go
Matthew Holt 2b5fd57259
Proper support for mixed timestamps and time zones
This will be a long-time WIP, but we now support full timestamps with local time offsets, absolute ones with UTC times only, and wall times only.

Several other fixes/enhancements. Making an effort to display time zone in time displays throughout the app.

Can now try to infer time zones during import, which is the default setting.

This will take a while to fully implement but it's a good start. Just have to be really careful about date crafting/manipulation/parsing.
2025-09-12 11:17:49 -06:00

168 lines
4.6 KiB
Go

/*
Timelinize
Copyright (c) 2013 Matthew Holt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package smsbackuprestore
import (
"encoding/xml"
"time"
"github.com/timelinize/timelinize/timeline"
)
// Smses was generated 2019-07-10 using an export from
// SMS Backup & Restore v10.05.602 (previous versions
// have a bug with emoji encodings).
type Smses struct {
XMLName xml.Name `xml:"smses"`
Text string `xml:",chardata"`
Count int `xml:"count,attr"`
Type string `xml:"type,attr"`
BackupSet string `xml:"backup_set,attr"` // UUID
BackupDate int64 `xml:"backup_date,attr"` // unix timestamp in milliseconds
SMS []SMS `xml:"sms"`
MMS []MMS `xml:"mms"`
}
// CommonSMSandMMSFields are the fields that both
// SMS and MMS share in common.
type CommonSMSandMMSFields struct {
Text string `xml:",chardata"`
Address string `xml:"address,attr"` // the phone number (or email address) -- I've seen some *weird* stuff in this field, don't rely on it
Date int64 `xml:"date,attr"` // unix timestamp in milliseconds
Read int `xml:"read,attr"`
Locked int `xml:"locked,attr"`
DateSent int64 `xml:"date_sent,attr"` // unix timestamp in (SMS: milliseconds, MMS: seconds)
SubID int `xml:"sub_id,attr"`
ReadableDate string `xml:"readable_date,attr"` // format: "Oct 20, 2017 12:35:30 PM"
ContactName string `xml:"contact_name,attr"` // might be "(Unknown)"
}
// within returns true if c.Date is within the given timeframe (it ignores item ID frames).
func (c CommonSMSandMMSFields) within(tf timeline.Timeframe) bool {
ts := time.UnixMilli(c.Date).UTC()
return tf.Contains(ts)
}
// SMS represents a simple text message.
type SMS struct {
CommonSMSandMMSFields
Protocol int `xml:"protocol,attr"`
Type int `xml:"type,attr"` // 1 = received, 2 = sent, 3 = draft, 4 = outbox, 5 = failed, 6 = queued
Subject string `xml:"subject,attr"`
Body string `xml:"body,attr"`
Toa string `xml:"toa,attr"`
ScToa string `xml:"sc_toa,attr"`
ServiceCenter string `xml:"service_center,attr"`
Status int `xml:"status,attr"` // -1 = none, 0 = complete, 32 = pending, 64 = failed
}
// people returns what is known about the sender and receiver.
func (s SMS) people(dsOpt Options) (sender, receiver timeline.Entity) {
// fill the person from the item
itemOwner := timeline.Entity{
Attributes: []timeline.Attribute{
{
Name: timeline.AttributePhoneNumber,
Value: s.Address,
Identity: true,
},
},
}
if s.ContactName != "(Unknown)" {
itemOwner.Name = s.ContactName
}
timelineOwner := timeline.Entity{
Attributes: []timeline.Attribute{
{
Name: timeline.AttributePhoneNumber,
Value: dsOpt.OwnerPhoneNumber,
Identity: true,
},
},
}
// return in the proper order
if s.Type == smsTypeSent {
return timelineOwner, itemOwner
}
return itemOwner, timelineOwner
}
func (s SMS) metadata() timeline.Metadata {
var msgType string
switch s.Type {
case smsTypeDraft:
msgType = "draft"
case smsTypeFailed:
msgType = "failed"
case smsTypeOutbox:
msgType = "outbox"
case smsTypeQueued:
msgType = "queued"
case smsTypeReceived:
msgType = "received"
case smsTypeSent:
msgType = "sent"
}
var status string
switch s.Status {
case smsStatusPending:
status = "pending"
case smsStatusComplete:
status = "complete"
case smsStatusFailed:
status = "failed"
}
var readStatus string
switch s.Read {
case read:
readStatus = "read"
case unread:
readStatus = "unread"
}
sc := s.ServiceCenter
if sc == null {
sc = ""
}
subj := s.Subject
if subj == null {
subj = ""
}
// ensure any/interface type so that nil check can succeed,
// instead of having non-nil interface having nil value
subID := any(&s.SubID)
if s.SubID == -1 {
subID = nil
}
return timeline.Metadata{
"Type": msgType,
"Subject": subj,
"Read": readStatus,
"Service center": sc,
"Sub ID": subID,
"Status": status,
}
}