package xmpp import ( "bytes" "encoding/base64" "github.com/pkg/errors" "io" "os" "strconv" "strings" "dev.narayana.im/narayana/telegabber/persistence" "dev.narayana.im/narayana/telegabber/xmpp/extensions" "dev.narayana.im/narayana/telegabber/xmpp/gateway" log "github.com/sirupsen/logrus" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) func logPacketType(p stanza.Packet) { log.Warnf("Ignoring packet: %T\n", p) } // HandleIq processes an incoming XMPP iq func HandleIq(s xmpp.Sender, p stanza.Packet) { iq, ok := p.(*stanza.IQ) if !ok { logPacketType(p) return } log.Debugf("%#v", iq) if iq.Type == "get" { _, ok := iq.Payload.(*extensions.IqVcardTemp) if ok { go handleGetVcardTempIq(s, iq) } } } // HandleMessage processes an incoming XMPP message func HandleMessage(s xmpp.Sender, p stanza.Packet) { msg, ok := p.(stanza.Message) if !ok { logPacketType(p) return } component, ok := s.(*xmpp.Component) if !ok { log.Error("Not a component") return } if msg.Type != "error" && msg.Body != "" { log.WithFields(log.Fields{ "from": msg.From, "to": msg.To, }).Warn("Message") log.Debugf("%#v", msg) fromJid, err := stanza.NewJid(msg.From) if err != nil { log.Error("Invalid from JID!") return } session, ok := sessions[fromJid.Bare()] if !ok { log.Error("Message from stranger") return } toParts := strings.Split(msg.To, "@") toID := toParts[0] if len(toParts) > 1 { toIDInt, err := strconv.ParseInt(toID, 10, 64) if err == nil { session.ProcessOutgoingMessage(toIDInt, msg.Body, msg.From) return } log.WithFields(log.Fields{ "toID": toID, }).Error(errors.Wrap(err, "Invalid to JID!")) } else if toID == gateway.Jid.Bare() { if strings.HasPrefix(msg.Body, "/") { response := session.ProcessTransportCommand(msg.Body, fromJid.Resource) if response != "" { gateway.SendMessage(msg.From, "", response, component) } return } } log.Warn("Unknown purpose of the message, skipping") } } // HandlePresence processes an incoming XMPP presence func HandlePresence(s xmpp.Sender, p stanza.Packet) { prs, ok := p.(stanza.Presence) if !ok { logPacketType(p) return } if prs.Type == "subscribe" { handleSubscription(s, prs) } if prs.To == gateway.Jid.Bare() { handlePresence(s, prs) } } func handleSubscription(s xmpp.Sender, p stanza.Presence) { log.WithFields(log.Fields{ "from": p.From, "to": p.To, }).Warn("Subscription request") log.Debugf("%#v", p) reply := stanza.Presence{Attrs: stanza.Attrs{ From: p.To, To: p.From, Id: p.Id, Type: "subscribed", }} component, ok := s.(*xmpp.Component) if !ok { log.Error("Not a component") return } _ = gateway.ResumableSend(component, reply) } func handlePresence(s xmpp.Sender, p stanza.Presence) { presenceType := p.Type if presenceType == "" { presenceType = "online" } component, ok := s.(*xmpp.Component) if !ok { log.Error("Not a component") return } log.WithFields(log.Fields{ "type": presenceType, "from": p.From, "to": p.To, }).Warn("Presence") log.Debugf("%#v", p) // create session fromJid, err := stanza.NewJid(p.From) if err != nil { log.Error("Invalid from JID!") return } bareFromJid := fromJid.Bare() session, ok := getTelegramInstance(bareFromJid, &persistence.Session{}, component) if !ok { return } switch p.Type { // destroy session case "unsubscribed", "unsubscribe": if session.Disconnect(fromJid.Resource, false) { sessionLock.Lock() delete(sessions, bareFromJid) sessionLock.Unlock() } // go offline case "unavailable", "error": session.Disconnect(fromJid.Resource, false) // go online case "probe", "", "online": // due to the weird implementation of go-tdlib wrapper, it won't // return the client instance until successful authorization go func() { err = session.Connect(fromJid.Resource) if err != nil { log.Error(errors.Wrap(err, "TDlib connection failure")) } else { for status := range session.StatusesRange() { go session.ProcessStatusUpdate( status.ID, status.XMPP, status.Description, gateway.SPImmed(false), ) } } }() } } func handleGetVcardTempIq(s xmpp.Sender, iq *stanza.IQ) { log.WithFields(log.Fields{ "from": iq.From, "to": iq.To, }).Warn("VCard request") fromJid, err := stanza.NewJid(iq.From) if err != nil { log.Error("Invalid from JID!") return } session, ok := sessions[fromJid.Bare()] if !ok { log.Error("IQ from stranger") return } toParts := strings.Split(iq.To, "@") toID, err := strconv.ParseInt(toParts[0], 10, 64) if err != nil { log.Error("Invalid IQ to") return } chat, user, err := session.GetContactByID(toID, nil) if err != nil { log.Error(err) return } vcard := extensions.IqVcardTemp{} if chat != nil { vcard.Fn.Text = chat.Title if chat.Photo != nil { path := chat.Photo.Small.Local.Path file, err := os.Open(path) // obtain the photo right now if still not downloaded if err != nil && !chat.Photo.Small.Local.IsDownloadingCompleted { tdFile, tdErr := session.DownloadFile(chat.Photo.Small.Id, 32, true) if tdErr == nil { path = tdFile.Local.Path file, err = os.Open(path) } } if err == nil { defer file.Close() buf := new(bytes.Buffer) binval := base64.NewEncoder(base64.StdEncoding, buf) _, err = io.Copy(binval, file) if err == nil { vcard.Photo.Type.Text = "image/jpeg" vcard.Photo.Binval.Text = buf.String() } else { log.Errorf("Error calculating hash: %v", path) } } else if path != "" { log.Errorf("Photo does not exist: %v", path) } else { log.Errorf("PHOTO: %#v", err.Error()) } } } if user != nil { vcard.Nickname.Text = user.Username vcard.N.Given.Text = user.FirstName vcard.N.Family.Text = user.LastName vcard.Tel.Number.Text = user.PhoneNumber } answer := stanza.IQ{ Attrs: stanza.Attrs{ From: iq.To, To: iq.From, Id: iq.Id, Type: "result", }, Payload: vcard, } log.Debugf("%#v", answer) component, ok := s.(*xmpp.Component) if !ok { log.Error("Not a component") return } _ = gateway.ResumableSend(component, &answer) }