diff --git a/telegram/utils.go b/telegram/utils.go index e9d86a3..0cae07e 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -85,10 +85,12 @@ const ( type MembersList int const ( - MembersListMembers MembersList = iota + MembersListNone MembersList = iota + MembersListMembers MembersListRestricted MembersListBanned MembersListBannedAndAdministrators + MembersListAdministrators ) const ( @@ -621,9 +623,7 @@ func (c *Client) updateMUCMembers(mucState *MUCState, chatID int64, members []*c myRole := "participant" for _, member := range members { - senderId := c.GetSenderId(member.MemberId) - nickname := c.GetMUCNickname(senderId) - affiliation, role := c.memberStatusToAffiliationAndRole(member.Status) + senderId, nickname, affiliation, role := c.TgMemberToMUCMember(member) mucState.Members[senderId] = &MUCMember{ Nickname: nickname, Affiliation: affiliation, @@ -2485,6 +2485,14 @@ func (c *Client) memberStatusToAffiliationAndRole(memberStatus client.ChatMember return "member", "participant" } +// TgMemberToMUCMember resolves useful data to generate a MUC member +func (c *Client) TgMemberToMUCMember(member *client.ChatMember) (senderId int64, nickname, affiliation, role string) { + senderId = c.GetSenderId(member.MemberId) + nickname = c.GetMUCNickname(senderId) + affiliation, role = c.memberStatusToAffiliationAndRole(member.Status) + return +} + func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, plain bool, toJid string) { sChatId := gateway.CHATNODE(chatID) var mucJid string @@ -2540,6 +2548,8 @@ func (c *Client) GetChatMembers(chatID int64, limited bool, query string, member filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}} case MembersListBannedAndAdministrators: filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}, &client.ChatMembersFilterAdministrators{}} + case MembersListAdministrators: + filters = []client.ChatMembersFilter{&client.ChatMembersFilterAdministrators{}} } limit := int32(9999) diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index d8aafd7..d864493 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -309,9 +309,9 @@ type EmptySubject struct { // QueryMucAdmin is from XEP-0045 type QueryMucAdmin struct { - XMLName xml.Name `xml:"http://jabber.org/protocol/muc#admin query"` - Item QueryMucAdminItem `xml:"item"` - ResultSet *stanza.ResultSet `xml:"set,omitempty"` + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#admin query"` + Items []*QueryMucAdminItem `xml:"item"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` } // QueryMucAdminItem is a child element from XEP-0045 diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 76ed019..34cac24 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -71,6 +71,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { go handleGetQueryRegister(s, iq) return } + queryMucAdmin, ok := iq.Payload.(*extensions.QueryMucAdmin) + if ok { + go handleGetQueryMucAdmin(s, iq, queryMucAdmin) + return + } } else if iq.Type == stanza.IQTypeSet { queryRegister, ok := iq.Payload.(*extensions.QueryRegister) if ok { @@ -1147,6 +1152,84 @@ func handleGetQueryRegister(s xmpp.Sender, iq *stanza.IQ) { } } +func handleGetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryMucAdmin) { + component, answer, ok := iqResultStub(s, iq) + if !ok { + return + } + defer gateway.ResumableSend(component, answer) + + bare, _, fromOk := gateway.SplitJID(iq.From) + if !fromOk { + iqAnswerSetError(answer, 400) + return + } + + session, sessionOk := sessions[bare] + if !sessionOk || !session.Session.MUC { + iqAnswerSetError(answer, 403) + return + } + + toID, toOk, toIsGroup := toToID(iq.To) + if !toOk || !toIsGroup { + iqAnswerSetError(answer, 406) + return + } + + if len(query.Items) != 1 { + iqAnswerSetError(answer, 400) + return + } + item := query.Items[0] + + var membersList telegram.MembersList + switch item.Role { + case "moderator": + membersList = telegram.MembersListAdministrators + case "participant": + membersList = telegram.MembersListMembers + } + switch item.Affiliation { + case "owner": + iqAnswerSetError(answer, 403) + return + case "admin": + membersList = telegram.MembersListAdministrators + case "member": + membersList = telegram.MembersListMembers + case "outcast": + membersList = telegram.MembersListBanned + } + + if membersList == telegram.MembersListNone { + iqAnswerSetError(answer, 400) + return + } + + payload := &extensions.QueryMucAdmin{} + answer.Payload = payload + + members, err := session.GetChatMembers(toID, false, "", membersList) + if err == nil { + for _, member := range members { + senderId, nickname, affiliation, role := session.TgMemberToMUCMember(member) + if item.Role != "" && role != item.Role { + continue + } + if item.Affiliation != "" && affiliation != item.Affiliation { + continue + } + payload.Items = append(payload.Items, &extensions.QueryMucAdminItem{ + Jid: gateway.CHATJID(senderId, true), + Nick: nickname, + Role: role, + Affiliation: affiliation, + }) + } + } +} + func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) { component, answer, ok := iqResultStub(s, iq) if !ok { @@ -1620,66 +1703,81 @@ func handleSetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer return } - var userID int64 - if query.Item.Jid != "" { - userID, _, _ = toToID(query.Item.Jid) - } else if query.Item.Nick != "" { - userID = session.GetMUCMemberIdByNickname(toID, query.Item.Nick) - } - if userID == 0 { - iqAnswerSetError(answer, 404) - return + // pre-bake all data to make it transactional as much as possible + type Item struct { + UserID int64 + Nick string + Status telegram.ChatMemberStatus } - nick := query.Item.Nick - if nick == "" { - nick = session.GetMUCNickname(userID) - } - - var status telegram.ChatMemberStatus - var integerPayload int64 - var stringPayload string - - switch query.Item.Role { - case "none": - status = telegram.ChatMemberStatusKicked - case "visitor": - status = telegram.ChatMemberStatusMuted - case "participant": - status = telegram.ChatMemberStatusUnmuted - case "moderator": - status = telegram.ChatMemberStatusPromoted - } - // affiliations have a higher priority over roles - switch query.Item.Affiliation { - case "none": - status = telegram.ChatMemberStatusKicked - case "outcast": - status = telegram.ChatMemberStatusBanned - case "member": - status = telegram.ChatMemberStatusUnmuted - case "admin": - status = telegram.ChatMemberStatusPromoted - case "owner": - iqAnswerSetError(answer, 403) - return - } - - // nothing has been detected - if status == telegram.ChatMemberStatusIllegal { - iqAnswerSetError(answer, 400) - return - } - - err := session.SetChatMemberStatus(toID, userID, status, integerPayload, stringPayload, nick) - if err != nil { - code, ok := telegram.GetErrorCode(err) - if !ok { - code = 500 + var items []Item + for _, item := range query.Items { + if item.Affiliation == "owner" { + iqAnswerSetError(answer, 403) + return + } + + var userID int64 + if item.Jid != "" { + userID, _, _ = toToID(item.Jid) + } else if item.Nick != "" { + userID = session.GetMUCMemberIdByNickname(toID, item.Nick) + } + if userID == 0 { + iqAnswerSetError(answer, 404) + return + } + + nick := session.GetMUCNickname(userID) + + var status telegram.ChatMemberStatus + + switch item.Role { + case "none": + status = telegram.ChatMemberStatusKicked + case "visitor": + status = telegram.ChatMemberStatusMuted + case "participant": + status = telegram.ChatMemberStatusUnmuted + case "moderator": + status = telegram.ChatMemberStatusPromoted + } + // affiliations have a higher priority over roles + switch item.Affiliation { + case "none": + status = telegram.ChatMemberStatusKicked + case "outcast": + status = telegram.ChatMemberStatusBanned + case "member": + status = telegram.ChatMemberStatusUnmuted + case "admin": + status = telegram.ChatMemberStatusPromoted + } + + // nothing has been detected + if status == telegram.ChatMemberStatusIllegal { + iqAnswerSetError(answer, 400) + return + } + + items = append(items, Item{ + UserID: userID, + Nick: nick, + Status: status, + }) + } + + for _, item := range items { + err := session.SetChatMemberStatus(toID, item.UserID, item.Status, 0, "", item.Nick) + if err != nil { + code, ok := telegram.GetErrorCode(err) + if !ok { + code = 500 + } + iqAnswerSetError(answer, int(code)) + answer.Error.Text = err.Error() + return } - iqAnswerSetError(answer, int(code)) - answer.Error.Text = err.Error() - return } }