diff --git a/telegram/client.go b/telegram/client.go index 22ad2b9..524ade3 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -91,8 +91,8 @@ type Client struct { XmppClientFeatures map[string]*[]string XmppClientFeaturesLock sync.Mutex - AvatarHashes map[int64]*HashedAvatar - AvatarHashesLock sync.Mutex + avatarHashes map[int64]*HashedAvatar + avatarHashesLock sync.Mutex locks clientLocks SendMessageLock sync.Mutex @@ -182,7 +182,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component lastMsgHashes: make(map[int64]uint64), lastMsgIds: make(map[int64]string), XmppClientFeatures: make(map[string]*[]string), - AvatarHashes: make(map[int64]*HashedAvatar), + avatarHashes: make(map[int64]*HashedAvatar), locks: clientLocks{ chatMessageLocks: make(map[int64]*sync.Mutex), }, diff --git a/telegram/utils.go b/telegram/utils.go index 109e16d..e041412 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -408,12 +408,12 @@ func (c *Client) getFileData(tgFile *client.File, typ byte) string { // SetEmptyAvatarHash puts a dummy value into the cache to avoid attempting to fetch surely missing avatars func (c *Client) SetEmptyAvatarHash(chatId int64) { - c.AvatarHashesLock.Lock() - c.AvatarHashes[chatId] = &HashedAvatar{ + c.avatarHashesLock.Lock() + c.avatarHashes[chatId] = &HashedAvatar{ Hash: "", File: 0, } - c.AvatarHashesLock.Unlock() + c.avatarHashesLock.Unlock() } // GetPhotoSize return at least a rough size @@ -431,12 +431,12 @@ func (c *Client) GetPhotoSize(photo *client.File) int64 { // GetPhotoSha1 computes the photo hash func (c *Client) GetPhotoSha1(photo *client.File, chatId int64) string { sha1 := c.getFileData(photo, typeFileDataSha1) - c.AvatarHashesLock.Lock() - c.AvatarHashes[chatId] = &HashedAvatar{ + c.avatarHashesLock.Lock() + c.avatarHashes[chatId] = &HashedAvatar{ Hash: sha1, File: photo.Id, } - c.AvatarHashesLock.Unlock() + c.avatarHashesLock.Unlock() return sha1 } @@ -445,6 +445,32 @@ func (c *Client) GetPhotoBase64(photo *client.File) string { return c.getFileData(photo, typeFileDataBase64) } +// GetHashedAvatar obtain the avatar hash from cache or requests the avatar file immediately to calculate it +func (c *Client) GetHashedAvatar(chatId int64) *HashedAvatar { + c.avatarHashesLock.Lock() + hashedAvatar, ok := c.avatarHashes[chatId] + c.avatarHashesLock.Unlock() + + if !ok { + log.Info("Could not find avatar in cache, fetching immediately") + + chat, _, err := c.GetContactByID(chatId, nil) + if err != nil || chat == nil || chat.Photo == nil { + return nil + } + + file := chat.Photo.Small + + sha1 := c.GetPhotoSha1(file, chatId) + hashedAvatar = &HashedAvatar{ + Hash: sha1, + File: file.Id, + } + } + + return hashedAvatar +} + // ProcessStatusUpdate sets contact status func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, oldArgs ...args.V) error { if !c.Online() { @@ -1776,6 +1802,12 @@ func (c *Client) SendMessageToGateway(chatId int64, message *client.Message, id } } } + + if isGroupchat { + for _, jid := range jids { + gateway.SendMUCStatusCode(jid, gateway.MUCJID(chatId), c.xmpp, 104) + } + } } } } else { diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index e6e2f10..04d13d2 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -221,9 +221,10 @@ type MessageXLegacyInviteExtension struct { // MessageXMucUserExtension is from XEP-0045 type MessageXMucUserExtension struct { - XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` - Invite MessageXMucUserInvite - Password string `xml:"password,omitempty"` + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` + Invite *MessageXMucUserInvite `xml:"invite,omitempty"` + Status *MessageXMucUserStatus `xml:"status,omitempty"` + Password string `xml:"password,omitempty"` } // MessageXMucUserInvite is from XEP-0045 @@ -240,6 +241,12 @@ type MessageXMucUserInviteContinue struct { Thread string `xml:"thread,attr,omitempty"` } +// MessageXMucUserStatus is from XEP-0486 +type MessageXMucUserStatus struct { + XMLName xml.Name `xml:"status"` + Code string `xml:"code,attr"` +} + // PresenceXMucUserExtension is from XEP-0045 type PresenceXMucUserExtension struct { XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index a305ac3..c6de4f8 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -89,7 +89,7 @@ func MUCJID(chatId int64) string { // SendMessage creates and sends a message stanza func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId string) { - sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId) + sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0) } // SendServiceMessage creates and sends a simple message stanza from transport @@ -98,7 +98,7 @@ func SendServiceMessage(to, body string, component *xmpp.Component) { if uuid, err := uuid.NewRandom(); err == nil { id = uuid.String() } - sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "", "") + sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "", "", 0) } // SendTextMessage creates and sends a simple message stanza @@ -107,27 +107,27 @@ func SendTextMessage(to, from, body string, component *xmpp.Component, isGroupch if uuid, err := uuid.NewRandom(); err == nil { id = uuid.String() } - sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", 0, "", "") + sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", 0, "", "", 0) } // SendErrorMessage creates and sends an error message stanza func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) { - sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "") + sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0) } // SendErrorMessageWithBody creates and sends an error message stanza with body payload func SendErrorMessageWithBody(to, from, body, errorText, id string, code int, isGroupchat bool, component *xmpp.Component) { - sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "") + sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "", "", 0) } // SendMessageWithOOB creates and sends a message stanza with OOB URL func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom, stanzaId string) { - sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId) + sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "", stanzaId, 0) } // SendSubjectMessage creates and sends a MUC subject func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) { - sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0, "", "") + sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0, "", "", 0) } // SendMessageMarker creates and sends a message stanza with a XEP-0333 marker @@ -135,15 +135,20 @@ func SendMessageMarker(to string, from string, component *xmpp.Component, marker sendMessageWrapper(to, from, "", "", "", "", component, nil, &marker{ Type: markerType, Id: markerId, - }, 0, "", "", false, false, false, false, "", 0, "", "") + }, 0, "", "", false, false, false, false, "", 0, "", "", 0) } // SendMUCInvite creates and send a MUC invitation message func SendMUCInvite(to string, from string, component *xmpp.Component, inviteFrom string) { - sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, false, false, false, "", 0, inviteFrom, "") + sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, false, false, false, "", 0, inviteFrom, "", 0) } -func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int, inviteFrom, stanzaId string) { +// SendMUCStatusCode creates a groupchat message with a muc#user status code +func SendMUCStatusCode(to string, from string, component *xmpp.Component, statusCode int64) { + sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, true, false, false, "", 0, "", "", statusCode) +} + +func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int, inviteFrom, stanzaId string, statusCode int64) { toJid, err := stanza.NewJid(to) if err != nil { log.WithFields(log.Fields{ @@ -297,15 +302,23 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component if replaceId != "" { message.Extensions = append(message.Extensions, extensions.Replace{Id: replaceId}) } + var userExt extensions.MessageXMucUserExtension if inviteFrom != "" { - message.Extensions = append(message.Extensions, extensions.MessageXMucUserExtension{ - Invite: extensions.MessageXMucUserInvite{ - From: inviteFrom, - }, - }, extensions.MessageXLegacyInviteExtension{ + userExt.Invite = &extensions.MessageXMucUserInvite{ + From: inviteFrom, + } + message.Extensions = append(message.Extensions, extensions.MessageXLegacyInviteExtension{ Jid: messageFrom, }) } + if statusCode != 0 { + userExt.Status = &extensions.MessageXMucUserStatus{ + Code: strconv.FormatInt(statusCode, 10), + } + } + if inviteFrom != "" || statusCode != 0 { + message.Extensions = append(message.Extensions, userExt) + } if stanzaId != "" { message.Extensions = append(message.Extensions, extensions.MessageStanzaId{ Id: stanzaId, diff --git a/xmpp/handlers.go b/xmpp/handlers.go index ac0e11e..8a8bc51 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -745,12 +745,12 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) { return } - toParts := strings.Split(iq.To, "@") - toID, err := strconv.ParseInt(toParts[0], 10, 64) - if err != nil { + toID, toOk, _ := toToID(iq.To) + if !toOk { log.Error("Invalid IQ to") return } + info, err := session.GetVcardInfo(toID) if err != nil { log.Error(err) @@ -826,27 +826,7 @@ func handleGetAvatarDataIq(s xmpp.Sender, iq *stanza.IQ, pubsub *stanza.PubSubGe defer gateway.ResumableSend(component, &answer) - hashedAvatar, ok := session.AvatarHashes[chatId] - if !ok { - log.Info("Could not find avatar in cache, fetching immediately") - - chat, _, err := session.GetContactByID(chatId, nil) - if err != nil || chat == nil || chat.Photo == nil { - return - } - - file := chat.Photo.Small - - sha1 := session.GetPhotoSha1(file, chatId) - hashedAvatar = &telegram.HashedAvatar{ - Hash: sha1, - File: file.Id, - } - - session.AvatarHashesLock.Lock() - session.AvatarHashes[chatId] = hashedAvatar - session.AvatarHashesLock.Unlock() - } + hashedAvatar := session.GetHashedAvatar(chatId) if id != "" && hashedAvatar.Hash != id { log.Infof("Cache contains %v hash for chat %v, but %v was requested; aborting", hashedAvatar.Hash, iq.To, id) @@ -963,6 +943,7 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) { "http://jabber.org/protocol/muc#stable_id", "jabber:iq:register", "urn:xmpp:sid:0", + "vcard-temp", ) fields := []*stanza.Field{ &stanza.Field{ @@ -982,6 +963,15 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) { }, } + hashedAvatar := session.GetHashedAvatar(toID) + if hashedAvatar != nil { + fields = append(fields, &stanza.Field{ + Var: "muc#roominfo_avatarhash", + Label: "Avatar hash", + ValuesList: []string{hashedAvatar.Hash}, + }) + } + disco.Form = stanza.NewForm(fields, "result") } } else if !toOk {