From 0b1cbda1cc20361b90846b6e8534e016288c301f Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 18 Feb 2024 02:48:02 -0500 Subject: [PATCH] Show member dropdowns in chat administration forms --- telegram/commands.go | 26 +++------ telegram/utils.go | 129 ++++++++++++++++++++++++++++++++++------- telegram/utils_test.go | 6 +- xmpp/handlers.go | 55 +++++++++++++++--- 4 files changed, 165 insertions(+), 51 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index a01a80e..3c899ed 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -70,6 +70,7 @@ var transportCommands = map[string]command{ var notForGroups = []ChatType{ChatTypeBasicGroup, ChatTypeSupergroup, ChatTypeChannel} var notForPM = []ChatType{ChatTypePrivate, ChatTypeSecret} +var notForPMAndBasic = []ChatType{ChatTypePrivate, ChatTypeSecret, ChatTypeBasicGroup} var onlyForSecret = []ChatType{ChatTypePrivate, ChatTypeBasicGroup, ChatTypeSupergroup, ChatTypeChannel} var chatCommands = map[string]command{ @@ -93,8 +94,8 @@ var chatCommands = map[string]command{ "invite": command{1, []string{"id or @username"}, "add user to current chat", ¬ForPM}, "link": command{0, []string{}, "get invite link for current chat", ¬ForPM}, "kick": command{1, []string{"id or @username"}, "remove user from current chat", ¬ForPM}, - "mute": command{1, []string{"id or @username", "hours"}, "mute user in current chat", ¬ForPM}, - "unmute": command{1, []string{"id or @username"}, "unrestrict user from current chat", ¬ForPM}, + "mute": command{1, []string{"id or @username", "hours"}, "mute user in current chat", ¬ForPMAndBasic}, + "unmute": command{1, []string{"id or @username"}, "unrestrict user from current chat", ¬ForPMAndBasic}, "ban": command{1, []string{"id or @username", "hours"}, "restrict @username from current chat for [hours] or forever", ¬ForPM}, "unban": command{1, []string{"id or @username"}, "unbans @username in current chat (and devotes from admins)", ¬ForPM}, "promote": command{1, []string{"id or @username", "title"}, "promote user to admin in current chat", ¬ForPM}, @@ -1100,30 +1101,17 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool) query = args[0] } - members, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{ - ChatId: chatID, - Limit: 9999, - Query: query, - Filter: &client.ChatMembersFilterMembers{}, - }) + members, err := c.GetChatMembers(chatID, false, query, MembersListMembers) if err != nil { return err.Error(), true } var entries []string - 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 - } + for _, member := range members { + senderId := c.GetSenderId(member.MemberId) entries = append(entries, fmt.Sprintf( "%v | role: %v", - c.formatContact(senderId), + c.FormatContact(senderId), member.Status.ChatMemberStatusType(), )) } diff --git a/telegram/utils.go b/telegram/utils.go index 2c8b00d..e7d16d6 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -46,6 +46,7 @@ type messageStub struct { } var errOffline = errors.New("TDlib instance is offline") +var errOverLimit = errors.New("Over limit") var spaceRegex = regexp.MustCompile(`\s+`) var replyRegex = regexp.MustCompile("\\A>>? ?([0-9]+)\\n") @@ -65,6 +66,16 @@ const ( ChatTypeChannel ) +// MembersList is an enum of member list filters +type MembersList int + +const ( + MembersListMembers MembersList = iota + MembersListRestricted + MembersListBanned + MembersListBannedAndAdministrators +) + // 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() { @@ -330,7 +341,8 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o return c.sendPresence(newArgs...) } -func (c *Client) formatContact(chatID int64) string { +// FormatContact retrieves a complete "full name (@usernames)" string for display +func (c *Client) FormatContact(chatID int64) string { if chatID == 0 { return "" } @@ -362,23 +374,27 @@ func (c *Client) formatContact(chatID int64) string { return str } -func (c *Client) getSenderId(message *client.Message) (senderId int64) { - if message.SenderId != nil { - switch message.SenderId.MessageSenderType() { - case client.TypeMessageSenderUser: - senderUser, _ := message.SenderId.(*client.MessageSenderUser) - senderId = senderUser.UserId - case client.TypeMessageSenderChat: - senderChat, _ := message.SenderId.(*client.MessageSenderChat) - senderId = senderChat.ChatId - } +func (c *Client) GetSenderId(sender client.MessageSender) (senderId int64) { + switch sender.MessageSenderType() { + case client.TypeMessageSenderUser: + senderUser, _ := sender.(*client.MessageSenderUser) + senderId = senderUser.UserId + case client.TypeMessageSenderChat: + senderChat, _ := sender.(*client.MessageSenderChat) + senderId = senderChat.ChatId } + return +} +func (c *Client) getMessageSenderId(message *client.Message) (senderId int64) { + if message.SenderId != nil { + senderId = c.GetSenderId(message.SenderId) + } return } func (c *Client) formatSender(message *client.Message) string { - return c.formatContact(c.getSenderId(message)) + return c.FormatContact(c.getMessageSenderId(message)) } func (c *Client) messageToStub(message *client.Message, preview bool, text string) *messageStub { @@ -428,7 +444,7 @@ func (c *Client) getMessageReply(message *client.Message, preview bool, noConten } gatewayReply = &gateway.Reply{ - Author: fmt.Sprintf("%v@%s", c.getSenderId(replyMsg), gateway.Jid.Full()), + Author: fmt.Sprintf("%v@%s", c.getMessageSenderId(replyMsg), gateway.Jid.Full()), Id: replyId, } } else if !noContent { @@ -445,7 +461,7 @@ func (c *Client) getMessageReply(message *client.Message, preview bool, noConten } tgReply = &messageStub{ - Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.formatContact(replyTo.ChatId), + Sender: c.formatOrigin(replyTo.Origin) + " @ " + c.FormatContact(replyTo.ChatId), Date: replyTo.OriginSendDate, Text: text, } @@ -515,14 +531,14 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string { switch origin.MessageOriginType() { case client.TypeMessageOriginUser: originUser := origin.(*client.MessageOriginUser) - return c.formatContact(originUser.SenderUserId) + return c.FormatContact(originUser.SenderUserId) case client.TypeMessageOriginChat: originChat := origin.(*client.MessageOriginChat) var signature string if originChat.AuthorSignature != "" { signature = fmt.Sprintf(" (%s)", originChat.AuthorSignature) } - return c.formatContact(originChat.SenderChatId) + signature + return c.FormatContact(originChat.SenderChatId) + signature case client.TypeMessageOriginHiddenUser: originUser := origin.(*client.MessageOriginHiddenUser) return originUser.SenderName @@ -532,7 +548,7 @@ func (c *Client) formatOrigin(origin client.MessageOrigin) string { if channel.AuthorSignature != "" { signature = fmt.Sprintf(" (%s)", channel.AuthorSignature) } - return c.formatContact(channel.ChatId) + signature + return c.FormatContact(channel.ChatId) + signature } return "Unknown origin type" } @@ -701,13 +717,13 @@ func (c *Client) messageContentToText(content client.MessageContent, chatId int6 text := "invited " if len(addMembers.MemberUserIds) > 0 { - text += c.formatContact(addMembers.MemberUserIds[0]) + text += c.FormatContact(addMembers.MemberUserIds[0]) } return text case client.TypeMessageChatDeleteMember: deleteMember, _ := content.(*client.MessageChatDeleteMember) - return "kicked " + c.formatContact(deleteMember.UserId) + return "kicked " + c.FormatContact(deleteMember.UserId) case client.TypeMessagePinMessage: pinMessage, _ := content.(*client.MessagePinMessage) return "pinned message: " + c.formatMessage(chatId, pinMessage.MessageId, preview, nil) @@ -857,7 +873,7 @@ func (c *Client) messageContentToText(content client.MessageContent, chatId int6 } case client.TypeMessageChatSetMessageAutoDeleteTime: ttl, _ := content.(*client.MessageChatSetMessageAutoDeleteTime) - name := c.formatContact(ttl.FromUserId) + name := c.FormatContact(ttl.FromUserId) if name == "" { if ttl.MessageAutoDeleteTime == 0 { return "The self-destruct timer was disabled" @@ -1654,3 +1670,76 @@ func (c *Client) usernamesToString(usernames []string) string { } return strings.Join(atUsernames, ", ") } + +// GetChatMembers retrieves a list of chat members. "Limited" mode works only if there are no more than 20 members at all +func (c *Client) GetChatMembers(chatID int64, limited bool, query string, membersList MembersList) ([]*client.ChatMember, error) { + var filters []client.ChatMembersFilter + switch membersList { + case MembersListMembers: + filters = []client.ChatMembersFilter{&client.ChatMembersFilterMembers{}} + case MembersListRestricted: + filters = []client.ChatMembersFilter{&client.ChatMembersFilterRestricted{}} + case MembersListBanned: + filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}} + case MembersListBannedAndAdministrators: + filters = []client.ChatMembersFilter{&client.ChatMembersFilterBanned{}, &client.ChatMembersFilterAdministrators{}} + } + + limit := int32(9999) + if limited { + limit = 20 + + chat, _, err := c.GetContactByID(chatID, nil) + if err != nil { + return nil, err + } else if chat == nil { + return nil, errors.New("Chat not found") + } + + chatType := chat.Type.ChatTypeType() + if chatType == client.TypeChatTypeBasicGroup { + basicGroupType, _ := chat.Type.(*client.ChatTypeBasicGroup) + fullInfo, err := c.client.GetBasicGroupFullInfo(&client.GetBasicGroupFullInfoRequest{ + BasicGroupId: basicGroupType.BasicGroupId, + }) + if err != nil { + return nil, err + } + + if len(fullInfo.Members) > int(limit) { + return nil, errOverLimit + } + + return fullInfo.Members, nil + } else if chatType == client.TypeChatTypeSupergroup { + supergroupType, _ := chat.Type.(*client.ChatTypeSupergroup) + fullInfo, err := c.client.GetSupergroupFullInfo(&client.GetSupergroupFullInfoRequest{ + SupergroupId: supergroupType.SupergroupId, + }) + if err != nil { + return nil, err + } + + if fullInfo.MemberCount > limit { + return nil, errOverLimit + } + } else { + return nil, errors.New("Inapplicable chat type") + } + } + + var members []*client.ChatMember + for _, filter := range filters { + chatMembers, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{ + ChatId: chatID, + Limit: limit, + Query: query, + Filter: filter, + }) + if err != nil { + return nil, err + } + members = append(members, chatMembers.Members...) + } + return members, nil +} diff --git a/telegram/utils_test.go b/telegram/utils_test.go index fa9c107..a0939cd 100644 --- a/telegram/utils_test.go +++ b/telegram/utils_test.go @@ -567,7 +567,7 @@ func TestMessageToPrefix7(t *testing.T) { func GetSenderIdEmpty(t *testing.T) { message := client.Message{} - senderId := (&Client{}).getSenderId(&message) + senderId := (&Client{}).getMessageSenderId(&message) if senderId != 0 { t.Errorf("Wrong sender id: %v", senderId) } @@ -579,7 +579,7 @@ func GetSenderIdUser(t *testing.T) { UserId: 42, }, } - senderId := (&Client{}).getSenderId(&message) + senderId := (&Client{}).getMessageSenderId(&message) if senderId != 42 { t.Errorf("Wrong sender id: %v", senderId) } @@ -591,7 +591,7 @@ func GetSenderIdChat(t *testing.T) { ChatId: -42, }, } - senderId := (&Client{}).getSenderId(&message) + senderId := (&Client{}).getMessageSenderId(&message) if senderId != -42 { t.Errorf("Wrong sender id: %v", senderId) } diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 3554394..945f119 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -790,23 +790,59 @@ func handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command dummyString := "" required = &dummyString } - fields = append(fields, &stanza.Field{ + + var fieldType string + var options []stanza.Option + if toOk && i == 0 { + switch command.Node { + case "mute", "kick", "ban", "promote", "unmute", "unban": + session, ok := sessions[bare] + if ok { + var membersList telegram.MembersList + switch command.Node { + case "unmute": + membersList = telegram.MembersListRestricted + case "unban": + membersList = telegram.MembersListBannedAndAdministrators + } + members, err := session.GetChatMembers(toId, true, "", membersList) + if err == nil { + fieldType = stanza.FieldTypeListSingle + for _, member := range members { + senderId := session.GetSenderId(member.MemberId) + options = append(options, stanza.Option{ + Label: session.FormatContact(senderId), + ValuesList: []string{strconv.FormatInt(senderId, 10)}, + }) + } + } + } + } + } + + field := stanza.Field{ Var: strconv.FormatInt(int64(i), 10), Label: arg, Required: required, - }) + Type: fieldType, + Options: options, + } + fields = append(fields, &field) + log.Debugf("field: %#v", field) + } + form := stanza.Form{ + Type: stanza.FormTypeForm, + Title: command.Node, + Instructions: []string{cmd.Description}, + Fields: fields, } answer.Payload = &stanza.Command{ SessionId: command.Node, Node: command.Node, Status: stanza.CommandStatusExecuting, - CommandElement: &stanza.Form{ - Type: stanza.FormTypeForm, - Title: command.Node, - Instructions: []string{cmd.Description}, - Fields: fields, - }, + CommandElement: &form, } + log.Debugf("form: %#v", form) } else { cmdString = "/" + command.Node } @@ -842,8 +878,9 @@ func handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command }, } - log.Debugf("command response: %#v", answer.Payload) } + + log.Debugf("command response: %#v", answer.Payload) } func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code int) {