|
| 1 | +package botapi |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + |
| 6 | + "github.com/gotd/td/telegram/peers" |
| 7 | + "github.com/gotd/td/tg" |
| 8 | +) |
| 9 | + |
| 10 | +// resolveChannel resolves a ChatID to its MTProto input channel. Member |
| 11 | +// management is only available in supergroups and channels. |
| 12 | +func (b *Bot) resolveChannel(ctx context.Context, chat ChatID) (tg.InputChannelClass, error) { |
| 13 | + p, err := b.resolvePeer(ctx, chat) |
| 14 | + if err != nil { |
| 15 | + return nil, err |
| 16 | + } |
| 17 | + ch, ok := p.(peers.Channel) |
| 18 | + if !ok { |
| 19 | + return nil, &Error{Code: 400, Description: "Bad Request: method is available only for supergroups and channels"} |
| 20 | + } |
| 21 | + return ch.InputChannel(), nil |
| 22 | +} |
| 23 | + |
| 24 | +// BanChatMemberOption configures a BanChatMember call. |
| 25 | +type BanChatMemberOption func(*banConfig) |
| 26 | + |
| 27 | +type banConfig struct { |
| 28 | + untilDate int |
| 29 | + revokeHistory bool |
| 30 | +} |
| 31 | + |
| 32 | +// WithBanUntil sets the Unix time when the user will be unbanned (0 or far in |
| 33 | +// the future means forever). |
| 34 | +func WithBanUntil(untilDate int) BanChatMemberOption { |
| 35 | + return func(c *banConfig) { c.untilDate = untilDate } |
| 36 | +} |
| 37 | + |
| 38 | +// WithRevokeMessages deletes all messages from the user being banned. |
| 39 | +func WithRevokeMessages() BanChatMemberOption { |
| 40 | + return func(c *banConfig) { c.revokeHistory = true } |
| 41 | +} |
| 42 | + |
| 43 | +// BanChatMember bans a user from a supergroup or channel. The bot must be an |
| 44 | +// administrator with the can_restrict_members right. |
| 45 | +func (b *Bot) BanChatMember(ctx context.Context, chat ChatID, userID int64, opts ...BanChatMemberOption) error { |
| 46 | + var cfg banConfig |
| 47 | + for _, o := range opts { |
| 48 | + o(&cfg) |
| 49 | + } |
| 50 | + |
| 51 | + channel, err := b.resolveChannel(ctx, chat) |
| 52 | + if err != nil { |
| 53 | + return err |
| 54 | + } |
| 55 | + user, err := b.resolveInputUser(ctx, userID) |
| 56 | + if err != nil { |
| 57 | + return err |
| 58 | + } |
| 59 | + |
| 60 | + if _, err := b.raw.ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{ |
| 61 | + Channel: channel, |
| 62 | + Participant: userToInputPeer(user), |
| 63 | + BannedRights: tg.ChatBannedRights{ |
| 64 | + ViewMessages: true, |
| 65 | + SendMessages: true, |
| 66 | + SendMedia: true, |
| 67 | + SendStickers: true, |
| 68 | + SendGifs: true, |
| 69 | + SendGames: true, |
| 70 | + SendInline: true, |
| 71 | + EmbedLinks: true, |
| 72 | + UntilDate: cfg.untilDate, |
| 73 | + }, |
| 74 | + }); err != nil { |
| 75 | + return asAPIError(err) |
| 76 | + } |
| 77 | + if cfg.revokeHistory { |
| 78 | + if _, err := b.raw.ChannelsDeleteParticipantHistory(ctx, &tg.ChannelsDeleteParticipantHistoryRequest{ |
| 79 | + Channel: channel, |
| 80 | + Participant: userToInputPeer(user), |
| 81 | + }); err != nil { |
| 82 | + return asAPIError(err) |
| 83 | + } |
| 84 | + } |
| 85 | + return nil |
| 86 | +} |
| 87 | + |
| 88 | +// UnbanChatMember removes a user's ban from a supergroup or channel, clearing |
| 89 | +// all restrictions. It does not re-add the user to the chat. |
| 90 | +func (b *Bot) UnbanChatMember(ctx context.Context, chat ChatID, userID int64) error { |
| 91 | + channel, err := b.resolveChannel(ctx, chat) |
| 92 | + if err != nil { |
| 93 | + return err |
| 94 | + } |
| 95 | + user, err := b.resolveInputUser(ctx, userID) |
| 96 | + if err != nil { |
| 97 | + return err |
| 98 | + } |
| 99 | + |
| 100 | + if _, err := b.raw.ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{ |
| 101 | + Channel: channel, |
| 102 | + Participant: userToInputPeer(user), |
| 103 | + BannedRights: tg.ChatBannedRights{}, // empty rights clear the ban |
| 104 | + }); err != nil { |
| 105 | + return asAPIError(err) |
| 106 | + } |
| 107 | + return nil |
| 108 | +} |
| 109 | + |
| 110 | +// RestrictChatMember restricts a user in a supergroup. permissions is an |
| 111 | +// allow-list; denied actions are turned into MTProto banned rights. untilDate is |
| 112 | +// a Unix time (0 means forever). |
| 113 | +func (b *Bot) RestrictChatMember(ctx context.Context, chat ChatID, userID int64, permissions ChatPermissions, untilDate int) error { |
| 114 | + channel, err := b.resolveChannel(ctx, chat) |
| 115 | + if err != nil { |
| 116 | + return err |
| 117 | + } |
| 118 | + user, err := b.resolveInputUser(ctx, userID) |
| 119 | + if err != nil { |
| 120 | + return err |
| 121 | + } |
| 122 | + |
| 123 | + if _, err := b.raw.ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{ |
| 124 | + Channel: channel, |
| 125 | + Participant: userToInputPeer(user), |
| 126 | + BannedRights: permissions.toBannedRights(untilDate), |
| 127 | + }); err != nil { |
| 128 | + return asAPIError(err) |
| 129 | + } |
| 130 | + return nil |
| 131 | +} |
| 132 | + |
| 133 | +// PromoteChatMember promotes or demotes a user in a supergroup or channel. The |
| 134 | +// granted rights are the true fields of rights; pass a zero value to demote. |
| 135 | +func (b *Bot) PromoteChatMember(ctx context.Context, chat ChatID, userID int64, rights ChatAdminRights) error { |
| 136 | + channel, err := b.resolveChannel(ctx, chat) |
| 137 | + if err != nil { |
| 138 | + return err |
| 139 | + } |
| 140 | + user, err := b.resolveInputUser(ctx, userID) |
| 141 | + if err != nil { |
| 142 | + return err |
| 143 | + } |
| 144 | + |
| 145 | + if _, err := b.raw.ChannelsEditAdmin(ctx, &tg.ChannelsEditAdminRequest{ |
| 146 | + Channel: channel, |
| 147 | + UserID: user, |
| 148 | + AdminRights: rights.toTg(), |
| 149 | + Rank: rights.CustomTitle, |
| 150 | + }); err != nil { |
| 151 | + return asAPIError(err) |
| 152 | + } |
| 153 | + return nil |
| 154 | +} |
| 155 | + |
| 156 | +// GetChatMember returns information about a member of a supergroup or channel. |
| 157 | +func (b *Bot) GetChatMember(ctx context.Context, chat ChatID, userID int64) (ChatMember, error) { |
| 158 | + channel, err := b.resolveChannel(ctx, chat) |
| 159 | + if err != nil { |
| 160 | + return nil, err |
| 161 | + } |
| 162 | + user, err := b.resolveInputUser(ctx, userID) |
| 163 | + if err != nil { |
| 164 | + return nil, err |
| 165 | + } |
| 166 | + |
| 167 | + res, err := b.raw.ChannelsGetParticipant(ctx, &tg.ChannelsGetParticipantRequest{ |
| 168 | + Channel: channel, |
| 169 | + Participant: userToInputPeer(user), |
| 170 | + }) |
| 171 | + if err != nil { |
| 172 | + return nil, asAPIError(err) |
| 173 | + } |
| 174 | + return chatMemberFromParticipant(res.Participant, usersByID(res.Users)), nil |
| 175 | +} |
| 176 | + |
| 177 | +// GetChatAdministrators returns the administrators of a supergroup or channel, |
| 178 | +// excluding other bots. |
| 179 | +func (b *Bot) GetChatAdministrators(ctx context.Context, chat ChatID) ([]ChatMember, error) { |
| 180 | + channel, err := b.resolveChannel(ctx, chat) |
| 181 | + if err != nil { |
| 182 | + return nil, err |
| 183 | + } |
| 184 | + |
| 185 | + res, err := b.raw.ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{ |
| 186 | + Channel: channel, |
| 187 | + Filter: &tg.ChannelParticipantsAdmins{}, |
| 188 | + Limit: 200, |
| 189 | + }) |
| 190 | + if err != nil { |
| 191 | + return nil, asAPIError(err) |
| 192 | + } |
| 193 | + participants, ok := res.AsModified() |
| 194 | + if !ok { |
| 195 | + return nil, &Error{Code: 500, Description: "Internal Server Error: unexpected participants response"} |
| 196 | + } |
| 197 | + |
| 198 | + users := usersByID(participants.Users) |
| 199 | + out := make([]ChatMember, 0, len(participants.Participants)) |
| 200 | + for _, p := range participants.Participants { |
| 201 | + out = append(out, chatMemberFromParticipant(p, users)) |
| 202 | + } |
| 203 | + return out, nil |
| 204 | +} |
| 205 | + |
| 206 | +// GetChatMemberCount returns the number of members in a supergroup or channel. |
| 207 | +func (b *Bot) GetChatMemberCount(ctx context.Context, chat ChatID) (int, error) { |
| 208 | + channel, err := b.resolveChannel(ctx, chat) |
| 209 | + if err != nil { |
| 210 | + return 0, err |
| 211 | + } |
| 212 | + |
| 213 | + res, err := b.raw.ChannelsGetParticipants(ctx, &tg.ChannelsGetParticipantsRequest{ |
| 214 | + Channel: channel, |
| 215 | + Filter: &tg.ChannelParticipantsRecent{}, |
| 216 | + Limit: 1, |
| 217 | + }) |
| 218 | + if err != nil { |
| 219 | + return 0, asAPIError(err) |
| 220 | + } |
| 221 | + participants, ok := res.AsModified() |
| 222 | + if !ok { |
| 223 | + return 0, &Error{Code: 500, Description: "Internal Server Error: unexpected participants response"} |
| 224 | + } |
| 225 | + return participants.Count, nil |
| 226 | +} |
| 227 | + |
| 228 | +// userToInputPeer wraps an input user as the input peer the participant-editing |
| 229 | +// methods expect. |
| 230 | +func userToInputPeer(u tg.InputUserClass) tg.InputPeerClass { |
| 231 | + switch v := u.(type) { |
| 232 | + case *tg.InputUser: |
| 233 | + return &tg.InputPeerUser{UserID: v.UserID, AccessHash: v.AccessHash} |
| 234 | + case *tg.InputUserFromMessage: |
| 235 | + return &tg.InputPeerUserFromMessage{Peer: v.Peer, MsgID: v.MsgID, UserID: v.UserID} |
| 236 | + case *tg.InputUserSelf: |
| 237 | + return &tg.InputPeerSelf{} |
| 238 | + default: |
| 239 | + return &tg.InputPeerEmpty{} |
| 240 | + } |
| 241 | +} |
0 commit comments