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