483 lines
9.6 KiB
Go
483 lines
9.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
const (
|
|
RECEIVED = 1
|
|
SENT = 2
|
|
)
|
|
|
|
type SMSType int
|
|
|
|
func (s SMSType) String() string {
|
|
switch s {
|
|
case RECEIVED:
|
|
return "From"
|
|
default:
|
|
return "To"
|
|
}
|
|
}
|
|
|
|
// Smses was generated 2022-10-16 10:53:24 by timmy on turin.narnian.us.
|
|
type customTime struct{ time.Time }
|
|
|
|
func (c *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
var (
|
|
value string
|
|
err error
|
|
date int64
|
|
)
|
|
|
|
err = d.DecodeElement(&value, &start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
date, err = strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*c = customTime{time.UnixMilli(date)}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *customTime) UnmarshalXMLAttr(attr xml.Attr) error {
|
|
date, err := strconv.ParseInt(attr.Value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*c = customTime{time.UnixMilli(date)}
|
|
|
|
return nil
|
|
}
|
|
|
|
type address string
|
|
|
|
func (a *address) UnmarshalXMLAttr(attr xml.Attr) error {
|
|
*a = address(cleanAddress(attr.Value))
|
|
|
|
return nil
|
|
}
|
|
|
|
func cleanAddress(addr string) string {
|
|
r := strings.NewReplacer("(", "", ")", "", " ", "", "-", "", "+1", "")
|
|
|
|
return strings.TrimLeft(r.Replace(addr), "1")
|
|
}
|
|
|
|
type sms struct {
|
|
DateSent customTime `xml:"date_sent,attr"`
|
|
Date customTime `xml:"date,attr"`
|
|
Address address `xml:"address,attr"`
|
|
ContactName string `xml:"contact_name,attr"`
|
|
Subject string `xml:"subject,attr"`
|
|
Text string `xml:"body,attr"`
|
|
Toa string `xml:"toa,attr"`
|
|
ScToa string `xml:"sc_toa,attr"`
|
|
ServiceCenter string `xml:"service_center,attr"`
|
|
ReadableDate string `xml:"readable_date,attr"`
|
|
Type SMSType `xml:"type,attr"`
|
|
Status int `xml:"status,attr"`
|
|
Protocol int `xml:"protocol,attr"`
|
|
Locked bool `xml:"locked,attr"`
|
|
Read bool `xml:"read,attr"`
|
|
}
|
|
|
|
func (s sms) String() string {
|
|
return fmt.Sprint(s.Date)
|
|
}
|
|
|
|
type attachment struct {
|
|
Mime string
|
|
Name string
|
|
data []byte
|
|
}
|
|
|
|
type mms struct {
|
|
Date customTime `xml:"date,attr"`
|
|
DateSent customTime `xml:"date_sent,attr"`
|
|
Contacts []Contact
|
|
Attachments []attachment
|
|
ImageResizeStatus string `xml:"image_resize_status,attr"`
|
|
Text string
|
|
Type SMSType `xml:"msg_box,attr"`
|
|
TextOnly bool `xml:"text_only,attr"`
|
|
Read bool `xml:"read,attr"`
|
|
Seen bool `xml:"seen,attr"`
|
|
}
|
|
|
|
type Contact struct {
|
|
Name string
|
|
Number string
|
|
Sender bool
|
|
}
|
|
|
|
type Message struct {
|
|
Date customTime
|
|
Type SMSType
|
|
DateSent customTime
|
|
Contacts []Contact
|
|
Attachments []attachment
|
|
Text string
|
|
}
|
|
|
|
func (m Message) Sent() bool {
|
|
return m.Type == SENT
|
|
}
|
|
|
|
func (m Message) Sender() Contact {
|
|
for _, v := range m.Contacts {
|
|
if v.Sender {
|
|
return v
|
|
}
|
|
}
|
|
|
|
if len(m.Contacts) > 0 {
|
|
return m.Contacts[0]
|
|
}
|
|
|
|
return Contact{}
|
|
}
|
|
|
|
func (m Message) Addresses() []string {
|
|
str := make([]string, 0, len(m.Contacts))
|
|
for _, v := range m.Contacts {
|
|
str = append(str, v.Number)
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
func (m Message) Names() []string {
|
|
str := make([]string, 0, len(m.Contacts))
|
|
for _, v := range m.Contacts {
|
|
str = append(str, v.Name)
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
func (m *mms) unmarshalXML(d *xml.Decoder, start *xml.StartElement) error {
|
|
var (
|
|
err error
|
|
data []byte
|
|
)
|
|
|
|
switch start.Name.Local {
|
|
case "parts":
|
|
tmp := struct {
|
|
A []struct {
|
|
Mime string `xml:"ct,attr"`
|
|
Text string `xml:"text,attr"`
|
|
Name string `xml:"Name,attr"`
|
|
Data string `xml:"data,attr"`
|
|
} `xml:"part"`
|
|
}{}
|
|
|
|
err = d.DecodeElement(&tmp, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, att := range tmp.A {
|
|
switch att.Mime {
|
|
case "application/smil":
|
|
continue
|
|
case "text/plain":
|
|
m.Text += att.Text
|
|
default:
|
|
data, err = base64.StdEncoding.DecodeString(att.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m.Attachments = append(m.Attachments, attachment{
|
|
Mime: att.Mime,
|
|
Name: att.Name,
|
|
data: data,
|
|
})
|
|
}
|
|
}
|
|
|
|
case "addrs":
|
|
tmp := struct {
|
|
A []struct {
|
|
Address address `xml:"address,attr"`
|
|
Type string `xml:"type"`
|
|
} `xml:"addr"`
|
|
}{}
|
|
|
|
err = d.DecodeElement(&tmp, start)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, v := range tmp.A {
|
|
m.Contacts = append(m.Contacts, Contact{Name: "", Number: string(v.Address), Sender: v.Type == "137"})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *mms) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
var (
|
|
err error
|
|
contacts []string
|
|
addresses []string
|
|
)
|
|
|
|
for _, attr := range start.Attr {
|
|
switch attr.Name.Local {
|
|
case "text_only":
|
|
m.TextOnly, err = strconv.ParseBool(attr.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "date":
|
|
m.Date = customTime{}
|
|
|
|
err = (&m.Date).UnmarshalXMLAttr(attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "read":
|
|
m.Read, err = strconv.ParseBool(attr.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "image_resize_status":
|
|
m.ImageResizeStatus = attr.Value
|
|
case "msg_box":
|
|
var num int
|
|
|
|
num, err = strconv.Atoi(attr.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m.Type = SMSType(num)
|
|
case "date_sent":
|
|
err = m.DateSent.UnmarshalXMLAttr(attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "seen":
|
|
m.Seen, err = strconv.ParseBool(attr.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "contact_name":
|
|
contacts = strings.Split(attr.Value, ", ")
|
|
case "address":
|
|
addresses = strings.Split(attr.Value, "~")
|
|
}
|
|
}
|
|
|
|
Contacts := make(map[string]string, len(addresses))
|
|
|
|
for i, v := range addresses {
|
|
if i < len(contacts) {
|
|
Contacts[v] = contacts[i]
|
|
}
|
|
}
|
|
|
|
cont := true
|
|
for cont {
|
|
token, _ := d.Token()
|
|
switch element := token.(type) {
|
|
case xml.StartElement:
|
|
err = m.unmarshalXML(d, &element)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case xml.EndElement:
|
|
if element.Name.Local == "mms" {
|
|
cont = false
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, v := range m.Contacts {
|
|
if v.Name == "" {
|
|
v.Name = Contacts[v.Number]
|
|
}
|
|
}
|
|
|
|
// pretty.Println(m,"\n\n")
|
|
// os.Exit(1)
|
|
return nil
|
|
}
|
|
|
|
type Smses struct {
|
|
Count int `xml:"count,attr"`
|
|
SMS []sms `xml:"sms"`
|
|
MMS []mms `xml:"mms"`
|
|
MSGs []Message `xml:"-"`
|
|
}
|
|
|
|
func (s *Smses) Merge() {
|
|
s.MSGs = make([]Message, 0, len(s.SMS)+len(s.MMS))
|
|
for _, msg := range s.SMS {
|
|
s.MSGs = append(s.MSGs, Message{
|
|
Date: msg.Date,
|
|
Type: msg.Type,
|
|
DateSent: msg.DateSent,
|
|
Contacts: []Contact{{Name: msg.ContactName, Number: string(msg.Address), Sender: msg.Type == SENT}},
|
|
Attachments: []attachment{},
|
|
Text: msg.Text,
|
|
})
|
|
}
|
|
|
|
for _, msg := range s.MMS {
|
|
s.MSGs = append(s.MSGs, Message{
|
|
Date: msg.Date,
|
|
Type: msg.Type,
|
|
DateSent: msg.DateSent,
|
|
Contacts: msg.Contacts,
|
|
Attachments: msg.Attachments,
|
|
Text: msg.Text,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *Smses) Map() map[string][]Message {
|
|
conversations := make(map[string][]Message, 128)
|
|
for _, m := range s.MSGs {
|
|
conversations[strings.Join(m.Addresses(), ", ")] = append(conversations[strings.Join(m.Addresses(), ", ")], m)
|
|
}
|
|
|
|
for _, m := range conversations {
|
|
slices.SortFunc(m, func(a, b Message) bool {
|
|
return a.Date.Before(b.Date.Time)
|
|
})
|
|
}
|
|
|
|
return conversations
|
|
}
|
|
|
|
func (s *Smses) deDupeMessages() {
|
|
slices.SortStableFunc(s.MSGs, func(a, b Message) bool {
|
|
return a.Date.Before(b.Date.Time)
|
|
})
|
|
slices.SortStableFunc(s.MSGs, func(a, b Message) bool {
|
|
return a.Type < b.Type
|
|
})
|
|
slices.SortStableFunc(s.MSGs, func(a, b Message) bool {
|
|
return strings.Join(a.Addresses(), "-") < strings.Join(b.Addresses(), "-")
|
|
})
|
|
slices.SortStableFunc(s.MSGs, func(a, b Message) bool {
|
|
return a.Text < b.Text
|
|
})
|
|
|
|
s.MSGs = slices.CompactFunc(s.MSGs, func(a, b Message) bool {
|
|
return a.Date == b.Date && a.Text == b.Text && slices.EqualFunc(a.Contacts, b.Contacts, func(s1, s2 Contact) bool {
|
|
return s1.Number == s2.Number
|
|
})
|
|
})
|
|
|
|
fmt.Printf("MSGs Count: %d\n", len(s.MSGs))
|
|
|
|
deDup := make(map[string][]Message, 30)
|
|
for _, m := range s.MSGs {
|
|
deDup[fmt.Sprintf("%v%v%v", m.Addresses(), m.Type, m.Text)] = append(deDup[fmt.Sprintf("%v%v%v", m.Addresses(), m.Type, m.Text)], m)
|
|
}
|
|
|
|
fmt.Printf("Count: %d\n", len(deDup))
|
|
|
|
for i, conversation := range deDup {
|
|
if len(conversation) != 1 {
|
|
slices.SortFunc(conversation, func(a, b Message) bool {
|
|
return a.Date.Before(b.Date.Time)
|
|
})
|
|
|
|
msgs := []Message{conversation[0]}
|
|
|
|
for index, msg := range conversation {
|
|
if index+1 == len(conversation) {
|
|
break
|
|
}
|
|
|
|
if msg.Date.Add(time.Hour*2) != conversation[index+1].Date.Time {
|
|
msgs = append(msgs, conversation[index+1])
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(msgs, func(a, b Message) bool {
|
|
return a.Date.Before(b.Date.Time)
|
|
})
|
|
|
|
deDup[i] = slices.CompactFunc(msgs, func(a, b Message) bool {
|
|
return a.Date == b.Date && a.Text == b.Text && slices.EqualFunc(a.Contacts, b.Contacts, func(s1, s2 Contact) bool {
|
|
return s1.Number == s2.Number
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
msg := new(Smses)
|
|
|
|
number := flag.String("number","","the number to pull messages from")
|
|
|
|
flag.Parse()
|
|
if number == nil || *number == "" {
|
|
return
|
|
}
|
|
|
|
if flag.NArg() < 1 {
|
|
fmt.Println("fail")
|
|
os.Exit(1)
|
|
}
|
|
|
|
file, err := os.Open(flag.Arg(0))
|
|
if err != nil {
|
|
fmt.Printf("You fail: %v\n", err)
|
|
os.Exit(2)
|
|
}
|
|
|
|
xmlFile := xml.NewDecoder(&xmlDecoder{file, nil, nil})
|
|
|
|
err = xmlFile.Decode(msg)
|
|
if err != nil {
|
|
fmt.Printf("Fuck: %v\n", err)
|
|
file.Close()
|
|
os.Exit(3)
|
|
}
|
|
|
|
file.Close()
|
|
|
|
msg.Merge()
|
|
|
|
fmt.Printf("MSGs Count: %d\n", len(msg.MSGs))
|
|
|
|
msg.deDupeMessages()
|
|
|
|
msgMap := msg.Map()
|
|
|
|
msgs := msgMap[*number]
|
|
|
|
fmt.Println(msgs)
|
|
|
|
err = tpl(msgs)
|
|
if err != nil {
|
|
fmt.Printf("Fuck: %v\n", err)
|
|
os.Exit(4)
|
|
}
|
|
}
|