Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
Bohdan Horbeshko | 4972cb6d5e | ||
Bohdan Horbeshko | 1e7e761c6c | ||
Bohdan Horbeshko | b8a57c06b6 | ||
Bohdan Horbeshko | 02578440cd | ||
Bohdan Horbeshko | 47fa7bca49 | ||
Bohdan Horbeshko | a0803123b2 | ||
Bohdan Horbeshko | b70bb53c6d | ||
Bohdan Horbeshko | 41503c7fd4 | ||
Bohdan Horbeshko | cdaaa75c96 | ||
Bohdan Horbeshko | b68c07025d | ||
Bohdan Horbeshko | e8bde73164 | ||
Bohdan Horbeshko | e77caf2c42 | ||
Bohdan Horbeshko | c1887e5a1e | ||
Bohdan Horbeshko | 93abbe834e | ||
Bohdan Horbeshko | 6c65ef9988 | ||
Bohdan Horbeshko | 4249a8bf41 | ||
Bohdan Horbeshko | f99f4f6acc | ||
Bohdan Horbeshko | 776993894a | ||
Bohdan Horbeshko | 9dbd487dae | ||
Bohdan Horbeshko | 7eaf28ad7c | ||
Bohdan Horbeshko | 63521b8f90 | ||
Bohdan Horbeshko | 7ef32096af | ||
Bohdan Horbeshko | 63f12202d0 | ||
Bohdan Horbeshko | 6abb7ff9c2 | ||
Bohdan Horbeshko | afa21e10be |
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@
|
|||
|
||||
COMMIT := $(shell git rev-parse --short HEAD)
|
||||
TD_COMMIT := "8517026415e75a8eec567774072cbbbbb52376c1"
|
||||
VERSION := "v1.8.2"
|
||||
VERSION := "v2.0.0-dev"
|
||||
MAKEOPTS := "-j4"
|
||||
|
||||
all:
|
||||
|
|
2
go.mod
2
go.mod
|
@ -33,5 +33,5 @@ require (
|
|||
nhooyr.io/websocket v1.6.5 // indirect
|
||||
)
|
||||
|
||||
replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f
|
||||
replace gosrc.io/xmpp => dev.narayana.im/narayana/go-xmpp v0.0.0-20220708184440-35d9cd68e55f
|
||||
replace github.com/zelenin/go-tdlib => dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615
|
||||
|
|
4
go.sum
4
go.sum
|
@ -3,6 +3,10 @@ dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615 h1:RRUZJSro
|
|||
dev.narayana.im/narayana/go-tdlib v0.0.0-20230730021136-47da33180615/go.mod h1:Xs8fXbk5n7VaPyrSs9DP7QYoBScWYsjX+lUcWmx1DIU=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f h1:6249ajbMjgYz53Oq0IjTvjHXbxTfu29Mj1J/6swRHs4=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220524203317-306b4ff58e8f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220708184440-35d9cd68e55f h1:aT50UsPH1dLje9CCAquRRhr7I9ZvL3kQU6WIWTe8PZ0=
|
||||
dev.narayana.im/narayana/go-xmpp v0.0.0-20220708184440-35d9cd68e55f/go.mod h1:L3NFMqYOxyLz3JGmgFyWf7r9htE91zVGiK40oW4RwdY=
|
||||
github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7 h1:GbV1Lv3lVHsSeKAqPTBem72OCsGjXntW4jfJdXciE+w=
|
||||
github.com/Arman92/go-tdlib v0.0.0-20191002071913-526f4e1d15f7/go.mod h1:ZzkRfuaFj8etIYMj/ECtXtgfz72RE6U+dos27b3XIwk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/agnivade/wasmbrowsertest v0.3.1/go.mod h1:zQt6ZTdl338xxRaMW395qccVE2eQm0SjC/SDz0mPWQI=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
|
|
@ -39,6 +39,7 @@ type Session struct {
|
|||
KeepOnline bool `yaml:":keeponline"`
|
||||
RawMessages bool `yaml:":rawmessages"`
|
||||
AsciiArrows bool `yaml:":asciiarrows"`
|
||||
MUC bool `yaml:":muc"`
|
||||
OOBMode bool `yaml:":oobmode"`
|
||||
Carbons bool `yaml:":carbons"`
|
||||
HideIds bool `yaml:":hideids"`
|
||||
|
@ -49,6 +50,7 @@ var configKeys = []string{
|
|||
"keeponline",
|
||||
"rawmessages",
|
||||
"asciiarrows",
|
||||
"muc",
|
||||
"oobmode",
|
||||
"carbons",
|
||||
"hideids",
|
||||
|
@ -124,6 +126,8 @@ func (s *Session) Get(key string) (string, error) {
|
|||
return fromBool(s.RawMessages), nil
|
||||
case "asciiarrows":
|
||||
return fromBool(s.AsciiArrows), nil
|
||||
case "muc":
|
||||
return fromBool(s.MUC), nil
|
||||
case "oobmode":
|
||||
return fromBool(s.OOBMode), nil
|
||||
case "carbons":
|
||||
|
@ -173,6 +177,13 @@ func (s *Session) Set(key string, value string) (string, error) {
|
|||
}
|
||||
s.AsciiArrows = b
|
||||
return value, nil
|
||||
case "muc":
|
||||
b, err := toBool(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.MUC = b
|
||||
return value, nil
|
||||
case "oobmode":
|
||||
b, err := toBool(value)
|
||||
if err != nil {
|
||||
|
|
|
@ -47,12 +47,14 @@ func TestSessionToMap(t *testing.T) {
|
|||
session := Session{
|
||||
Timezone: "klsf",
|
||||
RawMessages: true,
|
||||
MUC: true,
|
||||
OOBMode: true,
|
||||
}
|
||||
m := session.ToMap()
|
||||
sample := map[string]string{
|
||||
"timezone": "klsf",
|
||||
"keeponline": "false",
|
||||
"muc": "true",
|
||||
"rawmessages": "true",
|
||||
"asciiarrows": "false",
|
||||
"oobmode": "true",
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
goxmpp "gosrc.io/xmpp"
|
||||
)
|
||||
|
||||
var version string = "1.8.2"
|
||||
var version string = "2.0.0-dev"
|
||||
var commit string
|
||||
|
||||
var sm *goxmpp.StreamManager
|
||||
|
|
|
@ -41,6 +41,25 @@ type DelayedStatus struct {
|
|||
TimestampExpired int64
|
||||
}
|
||||
|
||||
// MUCState holds MUC metadata
|
||||
type MUCState struct {
|
||||
Resources map[string]bool
|
||||
Members map[int64]*MUCMember
|
||||
}
|
||||
|
||||
// MUCMember represents a MUC member
|
||||
type MUCMember struct {
|
||||
Nickname string
|
||||
Affiliation string
|
||||
}
|
||||
|
||||
func NewMUCState() *MUCState {
|
||||
return &MUCState{
|
||||
Resources: make(map[string]bool),
|
||||
Members: make(map[int64]*MUCMember),
|
||||
}
|
||||
}
|
||||
|
||||
// Client stores the metadata for lazily invoked TDlib instance
|
||||
type Client struct {
|
||||
client *client.Client
|
||||
|
@ -64,6 +83,8 @@ type Client struct {
|
|||
lastMsgHashes map[int64]uint64
|
||||
msgHashSeed maphash.Seed
|
||||
|
||||
mucCache map[int64]*MUCState
|
||||
|
||||
locks clientLocks
|
||||
SendMessageLock sync.Mutex
|
||||
}
|
||||
|
@ -73,6 +94,7 @@ type clientLocks struct {
|
|||
chatMessageLocks map[int64]*sync.Mutex
|
||||
resourcesLock sync.Mutex
|
||||
outboxLock sync.Mutex
|
||||
mucCacheLock sync.Mutex
|
||||
lastMsgHashesLock sync.Mutex
|
||||
|
||||
authorizerReadLock sync.Mutex
|
||||
|
@ -133,6 +155,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component
|
|||
Session: session,
|
||||
resources: make(map[string]bool),
|
||||
outbox: make(map[string]string),
|
||||
mucCache: make(map[int64]*MUCState),
|
||||
content: &conf.Content,
|
||||
cache: cache.NewCache(),
|
||||
options: options,
|
||||
|
|
|
@ -193,23 +193,6 @@ func (c *Client) unsubscribe(chatID int64) error {
|
|||
)
|
||||
}
|
||||
|
||||
func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message) {
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
message := messages[i]
|
||||
reply, _ := c.getMessageReply(message)
|
||||
|
||||
gateway.SendMessage(
|
||||
c.jid,
|
||||
strconv.FormatInt(chatID, 10),
|
||||
c.formatMessage(0, 0, false, message),
|
||||
strconv.FormatInt(message.Id, 10),
|
||||
c.xmpp,
|
||||
reply,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) usernameOrIDToID(username string) (int64, error) {
|
||||
userID, err := strconv.ParseInt(username, 10, 64)
|
||||
// couldn't parse the id, try to lookup as a username
|
||||
|
@ -1005,7 +988,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
|
|||
return err.Error(), true
|
||||
}
|
||||
|
||||
c.sendMessagesReverse(chatID, messages.Messages)
|
||||
c.sendMessagesReverse(chatID, messages.Messages, true, "")
|
||||
// get latest entries from history
|
||||
case "history":
|
||||
var limit int32 = 10
|
||||
|
@ -1016,32 +999,11 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
|
|||
}
|
||||
}
|
||||
|
||||
var newMessages *client.Messages
|
||||
var messages []*client.Message
|
||||
var err error
|
||||
var fromId int64
|
||||
for _ = range make([]struct{}, limit) { // safety limit
|
||||
if len(messages) > 0 {
|
||||
fromId = messages[len(messages)-1].Id
|
||||
}
|
||||
|
||||
newMessages, err = c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||
ChatId: chatID,
|
||||
FromMessageId: fromId,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return err.Error(), true
|
||||
}
|
||||
|
||||
messages = append(messages, newMessages.Messages...)
|
||||
|
||||
if len(newMessages.Messages) == 0 || len(messages) >= int(limit) {
|
||||
break
|
||||
}
|
||||
messages, err := c.getNLastMessages(chatID, limit)
|
||||
if err != nil {
|
||||
return err.Error(), true
|
||||
}
|
||||
|
||||
c.sendMessagesReverse(chatID, messages)
|
||||
c.sendMessagesReverse(chatID, messages, true, "")
|
||||
// chat members
|
||||
case "members":
|
||||
var query string
|
||||
|
|
|
@ -153,6 +153,13 @@ func (c *Client) updateHandler() {
|
|||
|
||||
// new user discovered
|
||||
func (c *Client) updateUser(update *client.UpdateUser) {
|
||||
// check if MUC nicknames should be updated
|
||||
cacheUser, ok := c.cache.GetUser(update.User.Id)
|
||||
if ok && (cacheUser.FirstName != update.User.FirstName || cacheUser.LastName != update.User.LastName) {
|
||||
newNickname := c.GetMUCNickname(update.User.Id)
|
||||
c.updateMUCsNickname(update.User.Id, newNickname)
|
||||
}
|
||||
|
||||
c.cache.SetUser(update.User.Id, update.User)
|
||||
show, status, presenceType := c.userStatusToText(update.User.Status, update.User.Id)
|
||||
go c.ProcessStatusUpdate(update.User.Id, status, show, gateway.SPType(presenceType))
|
||||
|
@ -265,7 +272,7 @@ func (c *Client) updateMessageContent(update *client.UpdateMessageContent) {
|
|||
markupFunction,
|
||||
))
|
||||
for _, jid := range jids {
|
||||
gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, false)
|
||||
gateway.SendMessage(jid, strconv.FormatInt(update.ChatId, 10), text, "e"+strconv.FormatInt(update.MessageId, 10), c.xmpp, nil, 0, false, false, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,10 +322,14 @@ func (c *Client) updateMessageSendFailed(update *client.UpdateMessageSendFailed)
|
|||
|
||||
// chat title changed
|
||||
func (c *Client) updateChatTitle(update *client.UpdateChatTitle) {
|
||||
chat, user, _ := c.GetContactByID(update.ChatId, nil)
|
||||
if c.Session.MUC && c.IsGroup(chat) {
|
||||
return
|
||||
}
|
||||
|
||||
gateway.SetNickname(c.jid, strconv.FormatInt(update.ChatId, 10), update.Title, c.xmpp)
|
||||
|
||||
// set also the status (for group chats only)
|
||||
chat, user, _ := c.GetContactByID(update.ChatId, nil)
|
||||
if user == nil {
|
||||
c.ProcessStatusUpdate(update.ChatId, update.Title, "chat", gateway.SPImmed(true))
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n")
|
|||
const newlineChar string = "\n"
|
||||
const messageHeaderSeparator string = " | "
|
||||
|
||||
const (
|
||||
ChatTypeOther byte = iota
|
||||
ChatTypePM
|
||||
ChatTypeGroup
|
||||
)
|
||||
|
||||
// GetContactByUsername resolves username to user id retrieves user and chat information
|
||||
func (c *Client) GetContactByUsername(username string) (*client.Chat, *client.User, error) {
|
||||
if !c.Online() {
|
||||
|
@ -121,10 +127,10 @@ func (c *Client) GetContactByID(id int64, chat *client.Chat) (*client.Chat, *cli
|
|||
return chat, user, nil
|
||||
}
|
||||
|
||||
// IsPM checks if a chat is PM
|
||||
func (c *Client) IsPM(id int64) (bool, error) {
|
||||
// GetChatType checks if a chat is PM or group
|
||||
func (c *Client) GetChatType(id int64) (byte, error) {
|
||||
if !c.Online() || id == 0 {
|
||||
return false, errOffline
|
||||
return ChatTypeOther, errOffline
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -135,7 +141,7 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
|||
ChatId: id,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
return ChatTypeOther, err
|
||||
}
|
||||
|
||||
c.cache.SetChat(id, chat)
|
||||
|
@ -143,9 +149,12 @@ func (c *Client) IsPM(id int64) (bool, error) {
|
|||
|
||||
chatType := chat.Type.ChatTypeType()
|
||||
if chatType == client.TypeChatTypePrivate || chatType == client.TypeChatTypeSecret {
|
||||
return true, nil
|
||||
return ChatTypePM, nil
|
||||
}
|
||||
return false, nil
|
||||
if c.IsGroup(chat) {
|
||||
return ChatTypeGroup, nil
|
||||
}
|
||||
return ChatTypeOther, nil
|
||||
}
|
||||
|
||||
func (c *Client) userStatusToText(status client.UserStatus, chatID int64) (string, string, string) {
|
||||
|
@ -217,6 +226,10 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|||
return err
|
||||
}
|
||||
|
||||
if chat != nil && c.Session.MUC && c.IsGroup(chat) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var photo string
|
||||
if chat != nil && chat.Photo != nil {
|
||||
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
||||
|
@ -290,6 +303,214 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|||
)
|
||||
}
|
||||
|
||||
// JoinMUC saves MUC join fact and sends initialization data
|
||||
func (c *Client) JoinMUC(chatId int64, resource string, limit int32) {
|
||||
// save the nickname in this MUC, also as a marker of join
|
||||
c.locks.mucCacheLock.Lock()
|
||||
mucState, ok := c.mucCache[chatId]
|
||||
if !ok || mucState == nil {
|
||||
mucState = NewMUCState()
|
||||
c.mucCache[chatId] = mucState
|
||||
}
|
||||
_, ok = mucState.Resources[resource]
|
||||
if ok {
|
||||
// already joined, initializing anyway
|
||||
} else {
|
||||
mucState.Resources[resource] = true
|
||||
}
|
||||
c.locks.mucCacheLock.Unlock()
|
||||
|
||||
c.sendMUCStatuses(chatId)
|
||||
|
||||
messages, err := c.getNLastMessages(chatId, limit)
|
||||
if err == nil {
|
||||
c.sendMessagesReverse(chatId, messages, false, c.jid+"/"+resource)
|
||||
}
|
||||
|
||||
c.sendMUCSubject(chatId, resource)
|
||||
}
|
||||
|
||||
func (c *Client) getFullName(user *client.User) string {
|
||||
fullName := user.FirstName
|
||||
if user.LastName != "" {
|
||||
fullName = fullName + " " + user.LastName
|
||||
}
|
||||
return fullName
|
||||
}
|
||||
|
||||
func (c *Client) sendMUCStatuses(chatID int64) {
|
||||
c.locks.mucCacheLock.Lock()
|
||||
defer c.locks.mucCacheLock.Unlock()
|
||||
mucState, ok := c.mucCache[chatID]
|
||||
if !ok || mucState == nil {
|
||||
mucState = NewMUCState()
|
||||
c.mucCache[chatID] = mucState
|
||||
}
|
||||
|
||||
sChatId := strconv.FormatInt(chatID, 10)
|
||||
myNickname := "me"
|
||||
if c.me != nil {
|
||||
myNickname = c.getFullName(c.me)
|
||||
}
|
||||
myAffiliation := "member"
|
||||
|
||||
members, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{
|
||||
ChatId: chatID,
|
||||
Limit: 200,
|
||||
Filter: &client.ChatMembersFilterMembers{},
|
||||
})
|
||||
if err == nil {
|
||||
gatewayJidSuffix := "@" + gateway.Jid.Full()
|
||||
|
||||
for _, member := range members.Members {
|
||||
var senderId int64
|
||||
switch member.MemberId.MessageSenderType() {
|
||||
case client.TypeMessageSenderUser:
|
||||
memberUser, _ := member.MemberId.(*client.MessageSenderUser)
|
||||
senderId = memberUser.UserId
|
||||
case client.TypeMessageSenderChat:
|
||||
memberChat, _ := member.MemberId.(*client.MessageSenderChat)
|
||||
senderId = memberChat.ChatId
|
||||
}
|
||||
|
||||
nickname := c.GetMUCNickname(senderId)
|
||||
affiliation := c.memberStatusToAffiliation(member.Status)
|
||||
mucState.Members[senderId] = &MUCMember{
|
||||
Nickname: nickname,
|
||||
Affiliation: affiliation,
|
||||
}
|
||||
|
||||
if c.me != nil && senderId == c.me.Id {
|
||||
myNickname = nickname
|
||||
myAffiliation = affiliation
|
||||
continue
|
||||
}
|
||||
|
||||
gateway.SendPresence(
|
||||
c.xmpp,
|
||||
c.jid,
|
||||
gateway.SPFrom(sChatId),
|
||||
gateway.SPResource(nickname),
|
||||
gateway.SPImmed(true),
|
||||
gateway.SPMUCAffiliation(affiliation),
|
||||
gateway.SPMUCJid(strconv.FormatInt(senderId, 10) + gatewayJidSuffix),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// according to the spec, own member entry should be sent the last
|
||||
gateway.SendPresence(
|
||||
c.xmpp,
|
||||
c.jid,
|
||||
gateway.SPFrom(sChatId),
|
||||
gateway.SPResource(myNickname),
|
||||
gateway.SPImmed(true),
|
||||
gateway.SPMUCAffiliation(myAffiliation),
|
||||
gateway.SPMUCStatusCodes([]uint16{100, 110, 210}),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) sendMUCSubject(chatID int64, resource string) {
|
||||
pin, err := c.client.GetChatPinnedMessage(&client.GetChatPinnedMessageRequest{
|
||||
ChatId: chatID,
|
||||
})
|
||||
mucJid := strconv.FormatInt(chatID, 10) + "@" + gateway.Jid.Bare()
|
||||
toJid := c.jid + "/" + resource
|
||||
if err == nil {
|
||||
gateway.SendSubjectMessage(
|
||||
toJid,
|
||||
mucJid + "/" + c.GetMUCNickname(c.GetSenderId(pin)),
|
||||
c.messageToText(pin, false),
|
||||
strconv.FormatInt(pin.Id, 10),
|
||||
c.xmpp,
|
||||
int64(pin.Date),
|
||||
)
|
||||
} else {
|
||||
gateway.SendSubjectMessage(toJid, mucJid, "", "", c.xmpp, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// GetMUCNickname generates a unique nickname for a MUC member
|
||||
func (c *Client) GetMUCNickname(chatID int64) string {
|
||||
return c.formatContact(chatID)
|
||||
}
|
||||
|
||||
func (c *Client) updateMUCsNickname(memberID int64, newNickname string) {
|
||||
c.locks.mucCacheLock.Lock()
|
||||
defer c.locks.mucCacheLock.Unlock()
|
||||
|
||||
for mucId, state := range c.mucCache {
|
||||
oldMember, ok := state.Members[memberID]
|
||||
if ok {
|
||||
state.Members[memberID] = &MUCMember{
|
||||
Nickname: newNickname,
|
||||
Affiliation: oldMember.Affiliation,
|
||||
}
|
||||
|
||||
sMucId := strconv.FormatInt(mucId, 10)
|
||||
unavailableStatusCodes := []uint16{303, 210}
|
||||
availableStatusCodes := []uint16{100, 210}
|
||||
if c.me != nil && memberID == c.me.Id {
|
||||
unavailableStatusCodes = append(unavailableStatusCodes, 110)
|
||||
availableStatusCodes = append(availableStatusCodes, 110)
|
||||
}
|
||||
gateway.SendPresence(
|
||||
c.xmpp,
|
||||
c.jid,
|
||||
gateway.SPType("unavailable"),
|
||||
gateway.SPFrom(sMucId),
|
||||
gateway.SPResource(oldMember.Nickname),
|
||||
gateway.SPImmed(true),
|
||||
gateway.SPMUCAffiliation(oldMember.Affiliation),
|
||||
gateway.SPMUCNick(newNickname),
|
||||
gateway.SPMUCStatusCodes(unavailableStatusCodes),
|
||||
)
|
||||
gateway.SendPresence(
|
||||
c.xmpp,
|
||||
c.jid,
|
||||
gateway.SPFrom(sMucId),
|
||||
gateway.SPResource(newNickname),
|
||||
gateway.SPImmed(true),
|
||||
gateway.SPMUCAffiliation(oldMember.Affiliation),
|
||||
gateway.SPMUCStatusCodes(availableStatusCodes),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MUCHasResource checks if a MUC was joined from a given resource
|
||||
func (c *Client) MUCHasResource(chatID int64, resource string) bool {
|
||||
c.locks.mucCacheLock.Lock()
|
||||
defer c.locks.mucCacheLock.Unlock()
|
||||
|
||||
mucState, ok := c.mucCache[chatID]
|
||||
if !ok || mucState == nil {
|
||||
return false
|
||||
}
|
||||
_, ok = mucState.Resources[resource]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetMyMUCNickname obtains this account's nickname in a given MUC
|
||||
func (c *Client) GetMyMUCNickname(chatID int64) (string, bool) {
|
||||
if c.me == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
c.locks.mucCacheLock.Lock()
|
||||
defer c.locks.mucCacheLock.Unlock()
|
||||
|
||||
mucState, ok := c.mucCache[chatID]
|
||||
if !ok || mucState == nil {
|
||||
return "", false
|
||||
}
|
||||
member, ok := mucState.Members[c.me.Id]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return member.Nickname, true
|
||||
}
|
||||
|
||||
func (c *Client) formatContact(chatID int64) string {
|
||||
if chatID == 0 {
|
||||
return ""
|
||||
|
@ -322,7 +543,8 @@ func (c *Client) formatContact(chatID int64) string {
|
|||
return str
|
||||
}
|
||||
|
||||
func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
||||
// GetSenderId extracts a sender id from a message
|
||||
func (c *Client) GetSenderId(message *client.Message) (senderId int64) {
|
||||
if message.SenderId != nil {
|
||||
switch message.SenderId.MessageSenderType() {
|
||||
case client.TypeMessageSenderUser:
|
||||
|
@ -338,7 +560,7 @@ func (c *Client) getSenderId(message *client.Message) (senderId int64) {
|
|||
}
|
||||
|
||||
func (c *Client) formatSender(message *client.Message) string {
|
||||
return c.formatContact(c.getSenderId(message))
|
||||
return c.formatContact(c.GetSenderId(message))
|
||||
}
|
||||
|
||||
func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply, replyMsg *client.Message) {
|
||||
|
@ -358,7 +580,7 @@ func (c *Client) getMessageReply(message *client.Message) (reply *gateway.Reply,
|
|||
replyId = strconv.FormatInt(message.ReplyToMessageId, 10)
|
||||
}
|
||||
reply = &gateway.Reply{
|
||||
Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()),
|
||||
Author: fmt.Sprintf("%v@%s", c.GetSenderId(replyMsg), gateway.Jid.Full()),
|
||||
Id: replyId,
|
||||
}
|
||||
}
|
||||
|
@ -851,13 +1073,13 @@ func (c *Client) countCharsInLines(lines *[]string) (count int) {
|
|||
}
|
||||
|
||||
func (c *Client) messageToPrefix(message *client.Message, previewString string, fileString string, replyMsg *client.Message) (string, int, int) {
|
||||
isPM, err := c.IsPM(message.ChatId)
|
||||
chatType, err := c.GetChatType(message.ChatId)
|
||||
if err != nil {
|
||||
log.Errorf("Could not determine if chat is PM: %v", err)
|
||||
log.Errorf("Could not determine chat type: %v", err)
|
||||
}
|
||||
isCarbonsEnabled := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons
|
||||
// with carbons, hide for all messages in PM and only for outgoing in group chats
|
||||
hideSender := isCarbonsEnabled && (message.IsOutgoing || isPM)
|
||||
hideSender := (isCarbonsEnabled && (message.IsOutgoing || chatType == ChatTypePM)) || (c.Session.MUC && chatType == ChatTypeGroup)
|
||||
|
||||
var replyStart, replyEnd int
|
||||
prefix := []string{}
|
||||
|
@ -878,7 +1100,7 @@ func (c *Client) messageToPrefix(message *client.Message, previewString string,
|
|||
}
|
||||
}
|
||||
}
|
||||
if !isPM || !c.Session.HideIds {
|
||||
if (chatType != ChatTypePM && !c.Session.MUC) || !c.Session.HideIds {
|
||||
prefix = append(prefix, directionChar+strconv.FormatInt(message.Id, 10))
|
||||
}
|
||||
// show sender in group chats
|
||||
|
@ -931,10 +1153,29 @@ func (c *Client) ensureDownloadFile(file *client.File) *client.File {
|
|||
return file
|
||||
}
|
||||
|
||||
// ProcessIncomingMessage transfers a message to XMPP side and marks it as read on Telegram side
|
||||
// ProcessIncomingMessage is a legacy wrapper for SendMessageToGateway aiming only PM messages
|
||||
func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
||||
isCarbon := gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons && message.IsOutgoing
|
||||
jids := c.getCarbonFullJids(isCarbon, "")
|
||||
c.SendMessageToGateway(chatId, message, "", false, "", []string{})
|
||||
}
|
||||
|
||||
// SendMessageToGateway transfers a message to XMPP side and marks it as read on Telegram side
|
||||
func (c *Client) SendMessageToGateway(chatId int64, message *client.Message, id string, delay bool, groupChatFrom string, groupChatTos []string) {
|
||||
var isCarbon bool
|
||||
var jids []string
|
||||
var isGroupchat bool
|
||||
var originalFrom string
|
||||
if len(groupChatTos) == 0 {
|
||||
isCarbon = gateway.MessageOutgoingPermissionVersion > 0 && c.Session.Carbons && message.IsOutgoing
|
||||
jids = c.getCarbonFullJids(isCarbon, "")
|
||||
} else {
|
||||
isGroupchat = true
|
||||
jids = groupChatTos
|
||||
|
||||
senderId := c.GetSenderId(message)
|
||||
if senderId != 0 {
|
||||
originalFrom = strconv.FormatInt(senderId, 10) + "@" + gateway.Jid.Full()
|
||||
}
|
||||
}
|
||||
|
||||
var text, oob, auxText string
|
||||
|
||||
|
@ -1006,13 +1247,29 @@ func (c *Client) ProcessIncomingMessage(chatId int64, message *client.Message) {
|
|||
})
|
||||
|
||||
// forward message to XMPP
|
||||
sId := strconv.FormatInt(message.Id, 10)
|
||||
sChatId := strconv.FormatInt(chatId, 10)
|
||||
var sId string
|
||||
if id == "" {
|
||||
sId = strconv.FormatInt(message.Id, 10)
|
||||
} else {
|
||||
sId = id
|
||||
}
|
||||
|
||||
var from string
|
||||
if groupChatFrom == "" {
|
||||
from = strconv.FormatInt(chatId, 10)
|
||||
} else {
|
||||
from = groupChatFrom
|
||||
}
|
||||
|
||||
var timestamp int64
|
||||
if delay {
|
||||
timestamp = int64(message.Date)
|
||||
}
|
||||
|
||||
for _, jid := range jids {
|
||||
gateway.SendMessageWithOOB(jid, sChatId, text, sId, c.xmpp, reply, oob, isCarbon)
|
||||
gateway.SendMessageWithOOB(jid, from, text, sId, c.xmpp, reply, timestamp, oob, isCarbon, isGroupchat, originalFrom)
|
||||
if auxText != "" {
|
||||
gateway.SendMessage(jid, sChatId, auxText, sId, c.xmpp, reply, isCarbon)
|
||||
gateway.SendMessage(jid, from, auxText, sId, c.xmpp, reply, timestamp, isCarbon, isGroupchat, originalFrom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1023,21 +1280,21 @@ func (c *Client) PrepareOutgoingMessageContent(text string) client.InputMessageC
|
|||
}
|
||||
|
||||
// ProcessOutgoingMessage executes commands or sends messages to mapped chats, returns message id
|
||||
func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64, replaceId int64) int64 {
|
||||
func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid string, replyId int64, replaceId int64, isGroupchat bool) *client.Message {
|
||||
if !c.Online() {
|
||||
// we're offline
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
if replaceId == 0 && (strings.HasPrefix(text, "/") || strings.HasPrefix(text, "!")) {
|
||||
// try to execute commands
|
||||
response, isCommand := c.ProcessChatCommand(chatID, text)
|
||||
if response != "" {
|
||||
c.returnMessage(returnJid, chatID, response)
|
||||
c.returnMessage(returnJid, chatID, response, 0, isGroupchat)
|
||||
}
|
||||
// do not send on success
|
||||
if isCommand {
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1059,27 +1316,31 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
|||
if c.content.Upload != "" && strings.HasPrefix(text, c.content.Upload) {
|
||||
response, err := http.Get(text)
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Failed to fetch the uploaded file", err)
|
||||
c.returnError(returnJid, chatID, "Failed to fetch the uploaded file", err, 500, isGroupchat)
|
||||
}
|
||||
if response != nil && response.Body != nil {
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != 200 {
|
||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("Received status code %v", response.StatusCode))
|
||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("Received status code %v", response.StatusCode), response.StatusCode, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "telegabber-*")
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Failed to create a temporary directory", err)
|
||||
c.returnError(returnJid, chatID, "Failed to create a temporary directory", err, 500, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
tempFile, err := os.Create(filepath.Join(tempDir, filepath.Base(text)))
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Failed to create a temporary file", err)
|
||||
c.returnError(returnJid, chatID, "Failed to create a temporary file", err, 500, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = io.Copy(tempFile, response.Body)
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Failed to write a temporary file", err)
|
||||
c.returnError(returnJid, chatID, "Failed to write a temporary file", err, 500, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
|
||||
file = &client.InputFileLocal{
|
||||
|
@ -1107,10 +1368,10 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
|||
InputMessageContent: content,
|
||||
})
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Not edited", err)
|
||||
return 0
|
||||
c.returnError(returnJid, chatID, "Not edited", err, 400, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
return tgMessage.Id
|
||||
return tgMessage
|
||||
}
|
||||
|
||||
tgMessage, err := c.client.SendMessage(&client.SendMessageRequest{
|
||||
|
@ -1119,18 +1380,30 @@ func (c *Client) ProcessOutgoingMessage(chatID int64, text string, returnJid str
|
|||
InputMessageContent: content,
|
||||
})
|
||||
if err != nil {
|
||||
c.returnError(returnJid, chatID, "Not sent", err)
|
||||
return 0
|
||||
c.returnError(returnJid, chatID, "Not sent", err, 400, isGroupchat)
|
||||
return nil
|
||||
}
|
||||
return tgMessage.Id
|
||||
return tgMessage
|
||||
}
|
||||
|
||||
func (c *Client) returnMessage(returnJid string, chatID int64, text string) {
|
||||
gateway.SendTextMessage(returnJid, strconv.FormatInt(chatID, 10), text, c.xmpp)
|
||||
func (c *Client) returnMessage(returnJid string, chatID int64, text string, code int, isGroupchat bool) {
|
||||
sChatId := strconv.FormatInt(chatID, 10)
|
||||
if isGroupchat {
|
||||
gateway.SendErrorMessage(returnJid, sChatId + "@" + gateway.Jid.Bare(), text, code, isGroupchat, c.xmpp)
|
||||
} else {
|
||||
gateway.SendTextMessage(returnJid, sChatId, text, c.xmpp)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) returnError(returnJid string, chatID int64, msg string, err error) {
|
||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("%s: %s", msg, err.Error()))
|
||||
func (c *Client) returnError(returnJid string, chatID int64, msg string, err error, code int, isGroupchat bool) {
|
||||
responseError, ok := err.(client.ResponseError)
|
||||
log.Debugf("responseError: %#v", responseError)
|
||||
if ok && responseError.Err != nil {
|
||||
if responseError.Err.Message == "Have no write access to the chat" {
|
||||
code = 403
|
||||
}
|
||||
}
|
||||
c.returnMessage(returnJid, chatID, fmt.Sprintf("%s: %s", msg, err.Error()), code, isGroupchat)
|
||||
}
|
||||
|
||||
func (c *Client) prepareOutgoingMessageContent(text string, file *client.InputFileLocal) client.InputMessageContent {
|
||||
|
@ -1228,6 +1501,36 @@ func (c *Client) getLastMessages(id int64, query string, from int64, count int32
|
|||
})
|
||||
}
|
||||
|
||||
func (c *Client) getNLastMessages(chatID int64, limit int32) ([]*client.Message, error) {
|
||||
var newMessages *client.Messages
|
||||
var messages []*client.Message
|
||||
var err error
|
||||
var fromId int64
|
||||
|
||||
for _ = range make([]struct{}, limit) { // safety limit
|
||||
if len(messages) > 0 {
|
||||
fromId = messages[len(messages)-1].Id
|
||||
}
|
||||
|
||||
newMessages, err = c.client.GetChatHistory(&client.GetChatHistoryRequest{
|
||||
ChatId: chatID,
|
||||
FromMessageId: fromId,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messages = append(messages, newMessages.Messages...)
|
||||
|
||||
if len(newMessages.Messages) == 0 || len(messages) >= int(limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
// DownloadFile actually obtains a file by id given by TDlib
|
||||
func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*client.File, error) {
|
||||
return c.client.DownloadFile(&client.DownloadFileRequest{
|
||||
|
@ -1285,7 +1588,7 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
log.Warnf("Coudln't retrieve private chat info: %v", err.Error())
|
||||
log.Warnf("Couldn't retrieve private chat info: %v", err.Error())
|
||||
}
|
||||
} else if chatType == client.TypeChatTypeBasicGroup {
|
||||
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
||||
|
@ -1295,7 +1598,7 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
|||
if err == nil {
|
||||
return fullInfo.Description
|
||||
} else {
|
||||
log.Warnf("Coudln't retrieve basic group info: %v", err.Error())
|
||||
log.Warnf("Couldn't retrieve basic group info: %v", err.Error())
|
||||
}
|
||||
} else if chatType == client.TypeChatTypeSupergroup {
|
||||
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||
|
@ -1305,12 +1608,68 @@ func (c *Client) GetChatDescription(chat *client.Chat) string {
|
|||
if err == nil {
|
||||
return fullInfo.Description
|
||||
} else {
|
||||
log.Warnf("Coudln't retrieve supergroup info: %v", err.Error())
|
||||
log.Warnf("Couldn't retrieve supergroup info: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetChatMemberCount obtains the member count depending on the chat type
|
||||
func (c *Client) GetChatMemberCount(chat *client.Chat) int32 {
|
||||
chatType := chat.Type.ChatTypeType()
|
||||
if chatType == client.TypeChatTypePrivate {
|
||||
return 2
|
||||
} else if chatType == client.TypeChatTypeBasicGroup {
|
||||
basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup)
|
||||
basicGroup, err := c.client.GetBasicGroup(&client.GetBasicGroupRequest{
|
||||
BasicGroupId: basicGroupType.BasicGroupId,
|
||||
})
|
||||
if err == nil {
|
||||
return basicGroup.MemberCount
|
||||
} else {
|
||||
log.Warnf("Couldn't retrieve basic group: %v", err.Error())
|
||||
}
|
||||
} else if chatType == client.TypeChatTypeSupergroup {
|
||||
supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup)
|
||||
supergroup, err := c.client.GetSupergroup(&client.GetSupergroupRequest{
|
||||
SupergroupId: supergroupType.SupergroupId,
|
||||
})
|
||||
if err == nil {
|
||||
return supergroup.MemberCount
|
||||
} else {
|
||||
log.Warnf("Couldn't retrieve supergroup: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetGroupChats obtains all group chats
|
||||
func (c *Client) GetGroupChats() []*client.Chat {
|
||||
var groupChats []*client.Chat
|
||||
|
||||
chats, err := c.client.GetChats(&client.GetChatsRequest{
|
||||
Limit: chatsLimit,
|
||||
})
|
||||
if err == nil {
|
||||
for _, id := range chats.ChatIds {
|
||||
chat, _, _ := c.GetContactByID(id, nil)
|
||||
if chat != nil && c.IsGroup(chat) {
|
||||
groupChats = append(groupChats, chat)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Could not retrieve chats: %v", err)
|
||||
}
|
||||
|
||||
return groupChats
|
||||
}
|
||||
|
||||
// IsGroup determines if a chat is eligible to be represented as MUC
|
||||
func (c *Client) IsGroup(chat *client.Chat) bool {
|
||||
typ := chat.Type.ChatTypeType()
|
||||
return typ == client.TypeChatTypeBasicGroup
|
||||
}
|
||||
|
||||
// subscribe to a Telegram ID
|
||||
func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
||||
var args []args.V
|
||||
|
@ -1321,6 +1680,10 @@ func (c *Client) subscribeToID(id int64, chat *client.Chat) {
|
|||
chat, _, _ = c.GetContactByID(id, nil)
|
||||
}
|
||||
if chat != nil {
|
||||
if c.Session.MUC && c.IsGroup(chat) {
|
||||
return
|
||||
}
|
||||
|
||||
args = append(args, gateway.SPNickname(chat.Title))
|
||||
|
||||
gateway.SetNickname(c.jid, strconv.FormatInt(id, 10), chat.Title, c.xmpp)
|
||||
|
@ -1378,6 +1741,10 @@ func (c *Client) UpdateChatNicknames() {
|
|||
for _, id := range c.cache.ChatsKeys() {
|
||||
chat, ok := c.cache.GetChat(id)
|
||||
if ok {
|
||||
if c.Session.MUC && c.IsGroup(chat) {
|
||||
continue
|
||||
}
|
||||
|
||||
newArgs := []args.V{
|
||||
gateway.SPFrom(strconv.FormatInt(id, 10)),
|
||||
gateway.SPNickname(chat.Title),
|
||||
|
@ -1504,3 +1871,59 @@ func (c *Client) usernamesToString(usernames []string) string {
|
|||
}
|
||||
return strings.Join(atUsernames, ", ")
|
||||
}
|
||||
|
||||
func (c *Client) memberStatusToAffiliation(memberStatus client.ChatMemberStatus) string {
|
||||
switch memberStatus.ChatMemberStatusType() {
|
||||
case client.TypeChatMemberStatusCreator:
|
||||
return "owner"
|
||||
case client.TypeChatMemberStatusAdministrator:
|
||||
return "admin"
|
||||
case client.TypeChatMemberStatusMember:
|
||||
return "member"
|
||||
case client.TypeChatMemberStatusRestricted:
|
||||
return "outcast"
|
||||
case client.TypeChatMemberStatusLeft:
|
||||
return "none"
|
||||
case client.TypeChatMemberStatusBanned:
|
||||
return "outcast"
|
||||
}
|
||||
return "member"
|
||||
}
|
||||
|
||||
func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, plain bool, toJid string) {
|
||||
sChatId := strconv.FormatInt(chatID, 10)
|
||||
var mucJid string
|
||||
if toJid != "" {
|
||||
mucJid = sChatId + "@" + gateway.Jid.Bare()
|
||||
}
|
||||
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
message := messages[i]
|
||||
|
||||
if plain {
|
||||
reply, _ := c.getMessageReply(message)
|
||||
|
||||
gateway.SendMessage(
|
||||
c.jid,
|
||||
sChatId,
|
||||
c.formatMessage(0, 0, false, message),
|
||||
strconv.FormatInt(message.Id, 10),
|
||||
c.xmpp,
|
||||
reply,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
)
|
||||
} else {
|
||||
c.SendMessageToGateway(
|
||||
chatID,
|
||||
message,
|
||||
"",
|
||||
true,
|
||||
mucJid + "/" + c.GetMUCNickname(c.GetSenderId(message)),
|
||||
[]string{toJid},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,6 +213,60 @@ type QueryRegisterRemove struct {
|
|||
XMLName xml.Name `xml:"remove"`
|
||||
}
|
||||
|
||||
// PresenceXMucUserExtension is from XEP-0045
|
||||
type PresenceXMucUserExtension struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"`
|
||||
Item PresenceXMucUserItem
|
||||
Statuses []PresenceXMucUserStatus
|
||||
}
|
||||
|
||||
// PresenceXMucUserItem is from XEP-0045
|
||||
type PresenceXMucUserItem struct {
|
||||
XMLName xml.Name `xml:"item"`
|
||||
Affiliation string `xml:"affiliation,attr"`
|
||||
Jid string `xml:"jid,attr"`
|
||||
Nick string `xml:"nick,attr,omitempty"`
|
||||
Role string `xml:"role,attr"`
|
||||
}
|
||||
|
||||
// PresenceXMucUserStatus is from XEP-0045
|
||||
type PresenceXMucUserStatus struct {
|
||||
XMLName xml.Name `xml:"status"`
|
||||
Code uint16 `xml:"code,attr"`
|
||||
}
|
||||
|
||||
// MessageDelay is from XEP-0203
|
||||
type MessageDelay struct {
|
||||
XMLName xml.Name `xml:"urn:xmpp:delay delay"`
|
||||
From string `xml:"from,attr"`
|
||||
Stamp string `xml:"stamp,attr"`
|
||||
}
|
||||
|
||||
// MessageDelayLegacy is from XEP-0203
|
||||
type MessageDelayLegacy struct {
|
||||
XMLName xml.Name `xml:"jabber:x:delay x"`
|
||||
From string `xml:"from,attr"`
|
||||
Stamp string `xml:"stamp,attr"`
|
||||
}
|
||||
|
||||
// MessageAddresses is from XEP-0033
|
||||
type MessageAddresses struct {
|
||||
XMLName xml.Name `xml:"http://jabber.org/protocol/address addresses"`
|
||||
Addresses []MessageAddress
|
||||
}
|
||||
|
||||
// MessageAddress is from XEP-0033
|
||||
type MessageAddress struct {
|
||||
XMLName xml.Name `xml:"address"`
|
||||
Type string `xml:"type,attr"`
|
||||
Jid string `xml:"jid,attr"`
|
||||
}
|
||||
|
||||
// EmptySubject is a dummy for MUCs to circumvent omitempty. Not registered as it would conflict with Subject field
|
||||
type EmptySubject struct {
|
||||
XMLName xml.Name `xml:"subject"`
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c PresenceNickExtension) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
|
@ -278,6 +332,21 @@ func (c QueryRegister) GetSet() *stanza.ResultSet {
|
|||
return c.ResultSet
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c PresenceXMucUserExtension) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c MessageDelay) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Namespace is a namespace!
|
||||
func (c MessageDelayLegacy) Namespace() string {
|
||||
return c.XMLName.Space
|
||||
}
|
||||
|
||||
// Name is a packet name
|
||||
func (ClientMessage) Name() string {
|
||||
return "message"
|
||||
|
@ -362,4 +431,28 @@ func init() {
|
|||
"jabber:iq:register",
|
||||
"query",
|
||||
}, QueryRegister{})
|
||||
|
||||
// presence muc user
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{
|
||||
"http://jabber.org/protocol/muc#user",
|
||||
"x",
|
||||
}, PresenceXMucUserExtension{})
|
||||
|
||||
// message delay
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"urn:xmpp:delay",
|
||||
"delay",
|
||||
}, MessageDelay{})
|
||||
|
||||
// legacy message delay
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"jabber:x:delay",
|
||||
"x",
|
||||
}, MessageDelayLegacy{})
|
||||
|
||||
// message addresses
|
||||
stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{
|
||||
"http://jabber.org/protocol/address",
|
||||
"addresses",
|
||||
}, MessageAddresses{})
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dev.narayana.im/narayana/telegabber/badger"
|
||||
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
||||
|
@ -42,26 +43,41 @@ var DirtySessions = false
|
|||
var MessageOutgoingPermissionVersion = 0
|
||||
|
||||
// SendMessage creates and sends a message stanza
|
||||
func SendMessage(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, isCarbon bool) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, "", isCarbon)
|
||||
func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, isCarbon, isGroupchat bool, originalFrom string) {
|
||||
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, "", isCarbon, isGroupchat, false, originalFrom, 0)
|
||||
}
|
||||
|
||||
// SendServiceMessage creates and sends a simple message stanza from transport
|
||||
func SendServiceMessage(to string, body string, component *xmpp.Component) {
|
||||
sendMessageWrapper(to, "", body, "", component, nil, "", false)
|
||||
func SendServiceMessage(to, body string, component *xmpp.Component) {
|
||||
sendMessageWrapper(to, "", body, "", "", "", component, nil, 0, "", false, false, false, "", 0)
|
||||
}
|
||||
|
||||
// SendTextMessage creates and sends a simple message stanza
|
||||
func SendTextMessage(to string, from string, body string, component *xmpp.Component) {
|
||||
sendMessageWrapper(to, from, body, "", component, nil, "", false)
|
||||
func SendTextMessage(to, from, body string, component *xmpp.Component) {
|
||||
sendMessageWrapper(to, from, body, "", "", "", component, nil, 0, "", false, false, false, "", 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, 0, "", false, isGroupchat, false, "", code)
|
||||
}
|
||||
|
||||
// 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, 0, "", false, isGroupchat, false, "", code)
|
||||
}
|
||||
|
||||
// SendMessageWithOOB creates and sends a message stanza with OOB URL
|
||||
func SendMessageWithOOB(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) {
|
||||
sendMessageWrapper(to, from, body, id, component, reply, oob, isCarbon)
|
||||
func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat bool, originalFrom string) {
|
||||
sendMessageWrapper(to, from, body, "", "", id, component, reply, timestamp, oob, isCarbon, isGroupchat, false, originalFrom, 0)
|
||||
}
|
||||
|
||||
func sendMessageWrapper(to string, from string, body string, id string, component *xmpp.Component, reply *Reply, oob string, isCarbon bool) {
|
||||
// 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, timestamp, "", false, true, true, "", 0)
|
||||
}
|
||||
|
||||
func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob string, isCarbon, isGroupchat, forceSubject bool, originalFrom string, errorCode int) {
|
||||
toJid, err := stanza.NewJid(to)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
|
@ -76,12 +92,17 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
var logFrom string
|
||||
var messageFrom string
|
||||
var messageTo string
|
||||
if from == "" {
|
||||
logFrom = componentJid
|
||||
messageFrom = componentJid
|
||||
} else {
|
||||
if isGroupchat {
|
||||
logFrom = from
|
||||
messageFrom = from + "@" + componentJid
|
||||
messageFrom = from
|
||||
} else {
|
||||
if from == "" {
|
||||
logFrom = componentJid
|
||||
messageFrom = componentJid
|
||||
} else {
|
||||
logFrom = from
|
||||
messageFrom = from + "@" + componentJid
|
||||
}
|
||||
}
|
||||
if isCarbon {
|
||||
messageTo = messageFrom
|
||||
|
@ -95,14 +116,51 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
"to": to,
|
||||
}).Warn("Got message")
|
||||
|
||||
var messageType stanza.StanzaType
|
||||
if errorCode != 0 {
|
||||
messageType = stanza.MessageTypeError
|
||||
} else if isGroupchat {
|
||||
messageType = stanza.MessageTypeGroupchat
|
||||
} else {
|
||||
messageType = stanza.MessageTypeChat
|
||||
}
|
||||
|
||||
message := stanza.Message{
|
||||
Attrs: stanza.Attrs{
|
||||
From: messageFrom,
|
||||
To: messageTo,
|
||||
Type: "chat",
|
||||
Type: messageType,
|
||||
Id: id,
|
||||
},
|
||||
Body: body,
|
||||
Subject: subject,
|
||||
Body: body,
|
||||
}
|
||||
if errorCode != 0 {
|
||||
message.Error = stanza.Err{
|
||||
Code: errorCode,
|
||||
Text: errorText,
|
||||
}
|
||||
switch errorCode {
|
||||
case 400:
|
||||
message.Error.Type = stanza.ErrorTypeModify
|
||||
message.Error.Reason = "bad-request"
|
||||
case 403:
|
||||
message.Error.Type = stanza.ErrorTypeAuth
|
||||
message.Error.Reason = "forbidden"
|
||||
case 404:
|
||||
message.Error.Type = stanza.ErrorTypeCancel
|
||||
message.Error.Reason = "item-not-found"
|
||||
case 406:
|
||||
message.Error.Type = stanza.ErrorTypeModify
|
||||
message.Error.Reason = "not-acceptable"
|
||||
case 500:
|
||||
message.Error.Type = stanza.ErrorTypeWait
|
||||
message.Error.Reason = "internal-server-error"
|
||||
default:
|
||||
log.Error("Unknown error code, falling back with empty reason")
|
||||
message.Error.Type = stanza.ErrorTypeCancel
|
||||
message.Error.Reason = "undefined-condition"
|
||||
}
|
||||
}
|
||||
|
||||
if oob != "" {
|
||||
|
@ -119,16 +177,43 @@ func sendMessageWrapper(to string, from string, body string, id string, componen
|
|||
message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End))
|
||||
}
|
||||
}
|
||||
if !isCarbon && toJid.Resource != "" {
|
||||
if !isGroupchat && !isCarbon && toJid.Resource != "" {
|
||||
message.Extensions = append(message.Extensions, stanza.HintNoCopy{})
|
||||
}
|
||||
if timestamp != 0 {
|
||||
var delayFrom string
|
||||
if isGroupchat {
|
||||
delayFrom, _, _ = SplitJID(from)
|
||||
}
|
||||
message.Extensions = append(message.Extensions, extensions.MessageDelay{
|
||||
From: delayFrom,
|
||||
Stamp: time.Unix(timestamp, 0).UTC().Format(time.RFC3339),
|
||||
})
|
||||
message.Extensions = append(message.Extensions, extensions.MessageDelayLegacy{
|
||||
From: delayFrom,
|
||||
Stamp: time.Unix(timestamp, 0).UTC().Format("20060102T15:04:05"),
|
||||
})
|
||||
}
|
||||
if originalFrom != "" {
|
||||
message.Extensions = append(message.Extensions, extensions.MessageAddresses{
|
||||
Addresses: []extensions.MessageAddress{
|
||||
extensions.MessageAddress{
|
||||
Type: "ofrom",
|
||||
Jid: originalFrom,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if subject == "" && forceSubject {
|
||||
message.Extensions = append(message.Extensions, extensions.EmptySubject{})
|
||||
}
|
||||
|
||||
if isCarbon {
|
||||
carbonMessage := extensions.ClientMessage{
|
||||
Attrs: stanza.Attrs{
|
||||
From: bareTo,
|
||||
To: to,
|
||||
Type: "chat",
|
||||
Type: messageType,
|
||||
},
|
||||
}
|
||||
carbonMessage.Extensions = append(carbonMessage.Extensions, extensions.CarbonSent{
|
||||
|
@ -240,6 +325,18 @@ var SPResource = args.NewString()
|
|||
// SPImmed skips queueing
|
||||
var SPImmed = args.NewBool(args.Default(true))
|
||||
|
||||
// SPMUCAffiliation is a XEP-0045 MUC affiliation
|
||||
var SPMUCAffiliation = args.NewString()
|
||||
|
||||
// SPMUCNick is a XEP-0045 MUC user nick
|
||||
var SPMUCNick = args.NewString()
|
||||
|
||||
// SPMUCJid is a real jid of a MUC member
|
||||
var SPMUCJid = args.NewString()
|
||||
|
||||
// SPMUCStatusCodes is a set of XEP-0045 MUC status codes
|
||||
var SPMUCStatusCodes = args.New()
|
||||
|
||||
func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
||||
var presenceFrom string
|
||||
if SPFrom.IsSet(args) {
|
||||
|
@ -295,6 +392,32 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence {
|
|||
})
|
||||
}
|
||||
}
|
||||
if SPMUCAffiliation.IsSet(args) {
|
||||
affiliation := SPMUCAffiliation.Get(args)
|
||||
if affiliation != "" {
|
||||
userExt := extensions.PresenceXMucUserExtension{
|
||||
Item: extensions.PresenceXMucUserItem{
|
||||
Affiliation: affiliation,
|
||||
Role: affilationToRole(affiliation),
|
||||
},
|
||||
}
|
||||
if SPMUCNick.IsSet(args) {
|
||||
userExt.Item.Nick = SPMUCNick.Get(args)
|
||||
}
|
||||
if SPMUCJid.IsSet(args) {
|
||||
userExt.Item.Jid = SPMUCJid.Get(args)
|
||||
}
|
||||
if SPMUCStatusCodes.IsSet(args) {
|
||||
statusCodes := SPMUCStatusCodes.Get(args).([]uint16)
|
||||
for _, statusCode := range statusCodes {
|
||||
userExt.Statuses = append(userExt.Statuses, extensions.PresenceXMucUserStatus{
|
||||
Code: statusCode,
|
||||
})
|
||||
}
|
||||
}
|
||||
presence.Extensions = append(presence.Extensions, userExt)
|
||||
}
|
||||
}
|
||||
|
||||
return presence
|
||||
}
|
||||
|
@ -377,3 +500,13 @@ func SplitJID(from string) (string, string, bool) {
|
|||
}
|
||||
return fromJid.Bare(), fromJid.Resource, true
|
||||
}
|
||||
|
||||
func affilationToRole(affilation string) string {
|
||||
switch affilation {
|
||||
case "owner", "admin":
|
||||
return "moderator"
|
||||
case "member":
|
||||
return "participant"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
|
333
xmpp/handlers.go
333
xmpp/handlers.go
|
@ -27,6 +27,12 @@ const (
|
|||
)
|
||||
const NodeVCard4 string = "urn:xmpp:vcard4"
|
||||
|
||||
type discoType int
|
||||
const (
|
||||
discoTypeInfo discoType = iota
|
||||
discoTypeItems
|
||||
)
|
||||
|
||||
func logPacketType(p stanza.Packet) {
|
||||
log.Warnf("Ignoring packet: %T\n", p)
|
||||
}
|
||||
|
@ -55,12 +61,12 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
_, ok = iq.Payload.(*stanza.DiscoInfo)
|
||||
if ok {
|
||||
go handleGetDiscoInfo(s, iq)
|
||||
go handleGetDisco(discoTypeInfo, s, iq)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*stanza.DiscoItems)
|
||||
if ok {
|
||||
go handleGetDiscoItems(s, iq)
|
||||
go handleGetDisco(discoTypeItems, s, iq)
|
||||
return
|
||||
}
|
||||
_, ok = iq.Payload.(*extensions.QueryRegister)
|
||||
|
@ -117,6 +123,26 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
|
||||
toID, ok := toToID(msg.To)
|
||||
if ok {
|
||||
toJid, err := stanza.NewJid(msg.To)
|
||||
if err != nil {
|
||||
log.Error("Invalid to JID!")
|
||||
return
|
||||
}
|
||||
|
||||
isGroupchat := msg.Type == "groupchat"
|
||||
|
||||
if session.Session.MUC && toJid.Resource != "" {
|
||||
chat, _, err := session.GetContactByID(toID, nil)
|
||||
if err == nil && session.IsGroup(chat) {
|
||||
if isGroupchat {
|
||||
gateway.SendErrorMessageWithBody(msg.From, msg.To, msg.Body, "", msg.Id, 400, true, component)
|
||||
} else {
|
||||
gateway.SendErrorMessage(msg.From, msg.To, "PMing room members is not supported, use the real JID", 406, true, component)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var reply extensions.Reply
|
||||
var fallback extensions.Fallback
|
||||
var replace extensions.Replace
|
||||
|
@ -128,7 +154,6 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
log.Debugf("replace: %#v", replace)
|
||||
|
||||
var replyId int64
|
||||
var err error
|
||||
text := msg.Body
|
||||
if len(reply.Id) > 0 {
|
||||
chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, reply.Id)
|
||||
|
@ -191,8 +216,8 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
|
||||
session.SendMessageLock.Lock()
|
||||
defer session.SendMessageLock.Unlock()
|
||||
tgMessageId := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId)
|
||||
if tgMessageId != 0 {
|
||||
tgMessage := session.ProcessOutgoingMessage(toID, text, msg.From, replyId, replaceId, isGroupchat)
|
||||
if tgMessage != nil {
|
||||
if replaceId != 0 {
|
||||
// not needed (is it persistent among clients though?)
|
||||
/* err = gateway.IdsDB.ReplaceIdPair(session.Session.Login, bare, replace.Id, msg.Id, tgMessageId)
|
||||
|
@ -201,11 +226,23 @@ func HandleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||
} */
|
||||
session.AddToOutbox(replace.Id, resource)
|
||||
} else {
|
||||
err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessageId, msg.Id)
|
||||
err = gateway.IdsDB.Set(session.Session.Login, bare, toID, tgMessage.Id, msg.Id)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to save ids %v/%v %v", toID, tgMessageId, msg.Id)
|
||||
log.Errorf("Failed to save ids %v/%v %v", toID, tgMessage.Id, msg.Id)
|
||||
}
|
||||
}
|
||||
|
||||
// pong groupchat messages back
|
||||
if isGroupchat && toJid.Resource == "" {
|
||||
session.SendMessageToGateway(
|
||||
toID,
|
||||
tgMessage,
|
||||
msg.Id,
|
||||
false,
|
||||
msg.To + "/" + session.GetMUCNickname(session.GetSenderId(tgMessage)),
|
||||
[]string{msg.From},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
// if a message failed to edit on Telegram side, match new XMPP ID with old Telegram ID anyway
|
||||
|
@ -281,7 +318,15 @@ func HandlePresence(s xmpp.Sender, p stanza.Packet) {
|
|||
}
|
||||
if prs.To == gateway.Jid.Bare() {
|
||||
handlePresence(s, prs)
|
||||
return
|
||||
}
|
||||
var mucExt stanza.MucPresence
|
||||
prs.Get(&mucExt)
|
||||
if mucExt.XMLName.Space != "" {
|
||||
handleMUCPresence(s, prs, mucExt)
|
||||
return
|
||||
}
|
||||
tryHandleMUCNicknameChange(s, prs)
|
||||
}
|
||||
|
||||
func handleSubscription(s xmpp.Sender, p stanza.Presence) {
|
||||
|
@ -391,6 +436,141 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) {
|
|||
}
|
||||
}
|
||||
|
||||
func handleMUCPresence(s xmpp.Sender, p stanza.Presence, mucExt stanza.MucPresence) {
|
||||
log.WithFields(log.Fields{
|
||||
"type": p.Type,
|
||||
"from": p.From,
|
||||
"to": p.To,
|
||||
}).Warn("MUC presence")
|
||||
log.Debugf("%#v", p)
|
||||
|
||||
if p.Type == "" {
|
||||
toBare, nickname, ok := gateway.SplitJID(p.To)
|
||||
if ok {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
// separate declaration is crucial for passing as pointer to defer
|
||||
var reply *stanza.Presence
|
||||
reply = &stanza.Presence{Attrs: stanza.Attrs{
|
||||
From: toBare,
|
||||
To: p.From,
|
||||
Id: p.Id,
|
||||
}}
|
||||
defer gateway.ResumableSend(component, reply)
|
||||
|
||||
if nickname == "" {
|
||||
presenceReplySetError(reply, 400)
|
||||
return
|
||||
}
|
||||
|
||||
chatId, ok := toToID(toBare)
|
||||
if !ok {
|
||||
presenceReplySetError(reply, 404)
|
||||
return
|
||||
}
|
||||
|
||||
fromBare, fromResource, ok := gateway.SplitJID(p.From)
|
||||
if !ok {
|
||||
presenceReplySetError(reply, 400)
|
||||
return
|
||||
}
|
||||
|
||||
session, ok := sessions[fromBare]
|
||||
if !ok || !session.Session.MUC {
|
||||
presenceReplySetError(reply, 407)
|
||||
return
|
||||
}
|
||||
|
||||
chat, _, err := session.GetContactByID(chatId, nil)
|
||||
if err != nil || !session.IsGroup(chat) {
|
||||
presenceReplySetError(reply, 404)
|
||||
return
|
||||
}
|
||||
|
||||
limit, ok := mucExt.History.MaxStanzas.Get()
|
||||
if !ok {
|
||||
limit = 20
|
||||
}
|
||||
session.JoinMUC(chatId, fromResource, int32(limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tryHandleMUCNicknameChange(s xmpp.Sender, p stanza.Presence) {
|
||||
log.WithFields(log.Fields{
|
||||
"type": p.Type,
|
||||
"from": p.From,
|
||||
"to": p.To,
|
||||
}).Warn("Nickname change presence?")
|
||||
log.Debugf("%#v", p)
|
||||
|
||||
if p.Type != "" {
|
||||
return
|
||||
}
|
||||
|
||||
toBare, nickname, ok := gateway.SplitJID(p.To)
|
||||
if !ok || nickname == "" {
|
||||
return
|
||||
}
|
||||
|
||||
fromBare, fromResource, ok := gateway.SplitJID(p.From)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
session, ok := sessions[fromBare]
|
||||
if !ok || !session.Session.MUC {
|
||||
return
|
||||
}
|
||||
|
||||
chatId, ok := toToID(toBare)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
chat, _, err := session.GetContactByID(chatId, nil)
|
||||
if err != nil || !session.IsGroup(chat) {
|
||||
return
|
||||
}
|
||||
|
||||
if !session.MUCHasResource(chatId, fromResource) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warn("🗿 Yes")
|
||||
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
from := toBare
|
||||
nickname, ok = session.GetMyMUCNickname(chatId)
|
||||
if ok {
|
||||
from = from+"/"+nickname
|
||||
}
|
||||
reply := &stanza.Presence{
|
||||
Attrs: stanza.Attrs{
|
||||
From: from,
|
||||
To: p.From,
|
||||
Id: p.Id,
|
||||
Type: stanza.PresenceTypeError,
|
||||
},
|
||||
Error: stanza.Err{
|
||||
Code: 406,
|
||||
Type: stanza.ErrorTypeModify,
|
||||
Reason: "not-acceptable",
|
||||
Text: "Telegram does not support changing nicknames per-chat. Issue a /setname command to the transport if you wish to change the global name",
|
||||
},
|
||||
}
|
||||
gateway.ResumableSend(component, reply)
|
||||
}
|
||||
|
||||
func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
||||
log.WithFields(log.Fields{
|
||||
"from": iq.From,
|
||||
|
@ -441,7 +621,7 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|||
_ = gateway.ResumableSend(component, &answer)
|
||||
}
|
||||
|
||||
func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
||||
func handleGetDisco(dt discoType, s xmpp.Sender, iq *stanza.IQ) {
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
|
@ -454,15 +634,90 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
return
|
||||
}
|
||||
|
||||
disco := answer.DiscoInfo()
|
||||
_, ok := toToID(iq.To)
|
||||
if ok {
|
||||
disco.AddIdentity("", "account", "registered")
|
||||
} else {
|
||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
disco.AddFeatures("jabber:iq:register")
|
||||
if dt == discoTypeInfo {
|
||||
disco := answer.DiscoInfo()
|
||||
toID, toOk := toToID(iq.To)
|
||||
if !toOk {
|
||||
disco.AddIdentity("Telegram Gateway", "gateway", "telegram")
|
||||
disco.AddFeatures("jabber:iq:register")
|
||||
}
|
||||
|
||||
var isMuc bool
|
||||
bare, _, fromOk := gateway.SplitJID(iq.From)
|
||||
if fromOk {
|
||||
session, sessionOk := sessions[bare]
|
||||
if sessionOk && session.Session.MUC {
|
||||
if toOk {
|
||||
chat, _, err := session.GetContactByID(toID, nil)
|
||||
if err == nil && session.IsGroup(chat) {
|
||||
isMuc = true
|
||||
disco.AddIdentity(chat.Title, "conference", "text")
|
||||
|
||||
disco.AddFeatures(
|
||||
"http://jabber.org/protocol/muc",
|
||||
"muc_persistent",
|
||||
"muc_hidden",
|
||||
"muc_membersonly",
|
||||
"muc_unmoderated",
|
||||
"muc_nonanonymous",
|
||||
"muc_unsecured",
|
||||
"http://jabber.org/protocol/muc#stable_id",
|
||||
)
|
||||
fields := []*stanza.Field{
|
||||
&stanza.Field{
|
||||
Var: "FORM_TYPE",
|
||||
Type: "hidden",
|
||||
ValuesList: []string{"http://jabber.org/protocol/muc#roominfo"},
|
||||
},
|
||||
&stanza.Field{
|
||||
Var: "muc#roominfo_description",
|
||||
Label: "Description",
|
||||
ValuesList: []string{session.GetChatDescription(chat)},
|
||||
},
|
||||
&stanza.Field{
|
||||
Var: "muc#roominfo_occupants",
|
||||
Label: "Number of occupants",
|
||||
ValuesList: []string{strconv.FormatInt(int64(session.GetChatMemberCount(chat)), 10)},
|
||||
},
|
||||
}
|
||||
|
||||
disco.Form = stanza.NewForm(fields, "result")
|
||||
}
|
||||
} else {
|
||||
disco.AddFeatures(
|
||||
stanza.NSDiscoItems,
|
||||
"http://jabber.org/protocol/muc#stable_id",
|
||||
)
|
||||
disco.AddIdentity("Telegram group chats", "conference", "text")
|
||||
}
|
||||
}
|
||||
}
|
||||
if toOk && !isMuc {
|
||||
disco.AddIdentity("", "account", "registered")
|
||||
}
|
||||
answer.Payload = disco
|
||||
} else if dt == discoTypeItems {
|
||||
disco := answer.DiscoItems()
|
||||
|
||||
_, ok := toToID(iq.To)
|
||||
if !ok {
|
||||
bare, _, ok := gateway.SplitJID(iq.From)
|
||||
if ok {
|
||||
// raw access, no need to create a new instance if not connected
|
||||
session, ok := sessions[bare]
|
||||
if ok && session.Session.MUC {
|
||||
bareJid := gateway.Jid.Bare()
|
||||
disco.AddItem(bareJid, "", "Telegram group chats")
|
||||
for _, chat := range session.GetGroupChats() {
|
||||
jid := strconv.FormatInt(chat.Id, 10) + "@" + bareJid
|
||||
disco.AddItem(jid, "", chat.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
answer.Payload = disco
|
||||
}
|
||||
answer.Payload = disco
|
||||
|
||||
log.Debugf("%#v", answer)
|
||||
|
||||
|
@ -475,30 +730,6 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ) {
|
|||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func handleGetDiscoItems(s xmpp.Sender, iq *stanza.IQ) {
|
||||
answer, err := stanza.NewIQ(stanza.Attrs{
|
||||
Type: stanza.IQTypeResult,
|
||||
From: iq.To,
|
||||
To: iq.From,
|
||||
Id: iq.Id,
|
||||
Lang: "en",
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create answer IQ: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
answer.Payload = answer.DiscoItems()
|
||||
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
log.Error("Not a component")
|
||||
return
|
||||
}
|
||||
|
||||
_ = gateway.ResumableSend(component, answer)
|
||||
}
|
||||
|
||||
func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) {
|
||||
component, ok := s.(*xmpp.Component)
|
||||
if !ok {
|
||||
|
@ -658,6 +889,28 @@ func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code
|
|||
}
|
||||
}
|
||||
|
||||
func presenceReplySetError(reply *stanza.Presence, code int) {
|
||||
reply.Type = stanza.PresenceTypeError
|
||||
reply.Error = stanza.Err{
|
||||
Code: code,
|
||||
}
|
||||
switch code {
|
||||
case 400:
|
||||
reply.Error.Type = stanza.ErrorTypeModify
|
||||
reply.Error.Reason = "jid-malformed"
|
||||
case 407:
|
||||
reply.Error.Type = stanza.ErrorTypeAuth
|
||||
reply.Error.Reason = "registration-required"
|
||||
case 404:
|
||||
reply.Error.Type = stanza.ErrorTypeCancel
|
||||
reply.Error.Reason = "item-not-found"
|
||||
default:
|
||||
log.Error("Unknown error code, falling back with empty reason")
|
||||
reply.Error.Type = stanza.ErrorTypeCancel
|
||||
reply.Error.Reason = "undefined-condition"
|
||||
}
|
||||
}
|
||||
|
||||
func toToID(to string) (int64, bool) {
|
||||
toParts := strings.Split(to, "@")
|
||||
if len(toParts) < 2 {
|
||||
|
|
Loading…
Reference in a new issue