sms/main.go
2022-10-21 14:23:42 -07:00

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)
}
}