Support bulk muc#admin operations on occupantsu

This commit is contained in:
Bohdan Horbeshko 2025-06-15 21:36:03 -04:00
parent cff81d227c
commit b1ecfc29cd
3 changed files with 172 additions and 64 deletions

View file

@ -85,10 +85,12 @@ const (
type MembersList int type MembersList int
const ( const (
MembersListMembers MembersList = iota MembersListNone MembersList = iota
MembersListMembers
MembersListRestricted MembersListRestricted
MembersListBanned MembersListBanned
MembersListBannedAndAdministrators MembersListBannedAndAdministrators
MembersListAdministrators
) )
const ( const (
@ -621,9 +623,7 @@ func (c *Client) updateMUCMembers(mucState *MUCState, chatID int64, members []*c
myRole := "participant" myRole := "participant"
for _, member := range members { for _, member := range members {
senderId := c.GetSenderId(member.MemberId) senderId, nickname, affiliation, role := c.TgMemberToMUCMember(member)
nickname := c.GetMUCNickname(senderId)
affiliation, role := c.memberStatusToAffiliationAndRole(member.Status)
mucState.Members[senderId] = &MUCMember{ mucState.Members[senderId] = &MUCMember{
Nickname: nickname, Nickname: nickname,
Affiliation: affiliation, Affiliation: affiliation,
@ -2485,6 +2485,14 @@ func (c *Client) memberStatusToAffiliationAndRole(memberStatus client.ChatMember
return "member", "participant" 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) { func (c *Client) sendMessagesReverse(chatID int64, messages []*client.Message, plain bool, toJid string) {
sChatId := gateway.CHATNODE(chatID) sChatId := gateway.CHATNODE(chatID)
var mucJid string var mucJid string
@ -2540,6 +2548,8 @@ func (c *Client) GetChatMembers(chatID int64, limited bool, query string, member
filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}} filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}}
case MembersListBannedAndAdministrators: case MembersListBannedAndAdministrators:
filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}, &client.ChatMembersFilterAdministrators{}} filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}, &client.ChatMembersFilterAdministrators{}}
case MembersListAdministrators:
filters = []client.ChatMembersFilter{&client.ChatMembersFilterAdministrators{}}
} }
limit := int32(9999) limit := int32(9999)

View file

@ -309,9 +309,9 @@ type EmptySubject struct {
// QueryMucAdmin is from XEP-0045 // QueryMucAdmin is from XEP-0045
type QueryMucAdmin struct { type QueryMucAdmin struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/muc#admin query"` XMLName xml.Name `xml:"http://jabber.org/protocol/muc#admin query"`
Item QueryMucAdminItem `xml:"item"` Items []*QueryMucAdminItem `xml:"item"`
ResultSet *stanza.ResultSet `xml:"set,omitempty"` ResultSet *stanza.ResultSet `xml:"set,omitempty"`
} }
// QueryMucAdminItem is a child element from XEP-0045 // QueryMucAdminItem is a child element from XEP-0045

View file

@ -71,6 +71,11 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) {
go handleGetQueryRegister(s, iq) go handleGetQueryRegister(s, iq)
return return
} }
queryMucAdmin, ok := iq.Payload.(*extensions.QueryMucAdmin)
if ok {
go handleGetQueryMucAdmin(s, iq, queryMucAdmin)
return
}
} else if iq.Type == stanza.IQTypeSet { } else if iq.Type == stanza.IQTypeSet {
queryRegister, ok := iq.Payload.(*extensions.QueryRegister) queryRegister, ok := iq.Payload.(*extensions.QueryRegister)
if ok { 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) { func handleSetQueryRegister(s xmpp.Sender, iq *stanza.IQ, query *extensions.QueryRegister) {
component, answer, ok := iqResultStub(s, iq) component, answer, ok := iqResultStub(s, iq)
if !ok { if !ok {
@ -1620,66 +1703,81 @@ func handleSetQueryMucAdmin(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer
return return
} }
var userID int64 // pre-bake all data to make it transactional as much as possible
if query.Item.Jid != "" { type Item struct {
userID, _, _ = toToID(query.Item.Jid) UserID int64
} else if query.Item.Nick != "" { Nick string
userID = session.GetMUCMemberIdByNickname(toID, query.Item.Nick) Status telegram.ChatMemberStatus
}
if userID == 0 {
iqAnswerSetError(answer, 404)
return
} }
nick := query.Item.Nick var items []Item
if nick == "" { for _, item := range query.Items {
nick = session.GetMUCNickname(userID) if item.Affiliation == "owner" {
} iqAnswerSetError(answer, 403)
return
var status telegram.ChatMemberStatus }
var integerPayload int64
var stringPayload string var userID int64
if item.Jid != "" {
switch query.Item.Role { userID, _, _ = toToID(item.Jid)
case "none": } else if item.Nick != "" {
status = telegram.ChatMemberStatusKicked userID = session.GetMUCMemberIdByNickname(toID, item.Nick)
case "visitor": }
status = telegram.ChatMemberStatusMuted if userID == 0 {
case "participant": iqAnswerSetError(answer, 404)
status = telegram.ChatMemberStatusUnmuted return
case "moderator": }
status = telegram.ChatMemberStatusPromoted
} nick := session.GetMUCNickname(userID)
// affiliations have a higher priority over roles
switch query.Item.Affiliation { var status telegram.ChatMemberStatus
case "none":
status = telegram.ChatMemberStatusKicked switch item.Role {
case "outcast": case "none":
status = telegram.ChatMemberStatusBanned status = telegram.ChatMemberStatusKicked
case "member": case "visitor":
status = telegram.ChatMemberStatusUnmuted status = telegram.ChatMemberStatusMuted
case "admin": case "participant":
status = telegram.ChatMemberStatusPromoted status = telegram.ChatMemberStatusUnmuted
case "owner": case "moderator":
iqAnswerSetError(answer, 403) status = telegram.ChatMemberStatusPromoted
return }
} // affiliations have a higher priority over roles
switch item.Affiliation {
// nothing has been detected case "none":
if status == telegram.ChatMemberStatusIllegal { status = telegram.ChatMemberStatusKicked
iqAnswerSetError(answer, 400) case "outcast":
return status = telegram.ChatMemberStatusBanned
} case "member":
status = telegram.ChatMemberStatusUnmuted
err := session.SetChatMemberStatus(toID, userID, status, integerPayload, stringPayload, nick) case "admin":
if err != nil { status = telegram.ChatMemberStatusPromoted
code, ok := telegram.GetErrorCode(err) }
if !ok {
code = 500 // 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
} }
} }