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
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)

View file

@ -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

View file

@ -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
}
}