@@ -2,10 +2,341 @@ package botapi
22
33import (
44 "context"
5+ "strconv"
56
67 "github.com/gotd/td/tg"
78)
89
10+ // topicCreateService extracts the service message that a forum-topic creation
11+ // produced from the RPC's Updates response.
12+ func topicCreateService (resp tg.UpdatesClass ) (* tg.MessageService , bool ) {
13+ var ups []tg.UpdateClass
14+
15+ switch u := resp .(type ) {
16+ case * tg.Updates :
17+ ups = u .Updates
18+ case * tg.UpdatesCombined :
19+ ups = u .Updates
20+ default :
21+ return nil , false
22+ }
23+
24+ for _ , up := range ups {
25+ var msg tg.MessageClass
26+
27+ switch m := up .(type ) {
28+ case * tg.UpdateNewChannelMessage :
29+ msg = m .Message
30+ case * tg.UpdateNewMessage :
31+ msg = m .Message
32+ default :
33+ continue
34+ }
35+
36+ if svc , ok := msg .(* tg.MessageService ); ok {
37+ return svc , true
38+ }
39+ }
40+
41+ return nil , false
42+ }
43+
44+ // generalForumTopicID is the topic id of the special "General" topic that every
45+ // forum supergroup has. The general-topic methods operate on it.
46+ const generalForumTopicID = 1
47+
48+ // ForumTopic is the result of CreateForumTopic: a topic in a forum supergroup.
49+ type ForumTopic struct {
50+ // MessageThreadID is the unique identifier of the forum topic.
51+ MessageThreadID int `json:"message_thread_id"`
52+ // Name is the topic name.
53+ Name string `json:"name"`
54+ // IconColor is the color of the topic icon in RGB format.
55+ IconColor int `json:"icon_color"`
56+ // IconCustomEmojiID is the unique identifier of the custom emoji shown as the
57+ // topic icon, if any.
58+ IconCustomEmojiID string `json:"icon_custom_emoji_id,omitempty"`
59+ }
60+
61+ // forumTopicConfig accumulates the optional parameters shared by CreateForumTopic
62+ // and EditForumTopic.
63+ type forumTopicConfig struct {
64+ name string
65+ nameSet bool
66+ iconColor int
67+ iconColorSet bool
68+ iconEmoji string
69+ iconEmojiSet bool
70+ }
71+
72+ // ForumTopicOption configures CreateForumTopic / EditForumTopic.
73+ type ForumTopicOption func (* forumTopicConfig )
74+
75+ // WithForumTopicName sets the topic name. Used by EditForumTopic; CreateForumTopic
76+ // takes the name as a positional argument.
77+ func WithForumTopicName (name string ) ForumTopicOption {
78+ return func (c * forumTopicConfig ) {
79+ c .name = name
80+ c .nameSet = true
81+ }
82+ }
83+
84+ // WithForumTopicIconColor sets the color of the topic icon in RGB format. One of
85+ // 0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, or 0xFB6F5F. Only honored on
86+ // creation.
87+ func WithForumTopicIconColor (color int ) ForumTopicOption {
88+ return func (c * forumTopicConfig ) {
89+ c .iconColor = color
90+ c .iconColorSet = true
91+ }
92+ }
93+
94+ // WithForumTopicIconCustomEmojiID sets the custom emoji shown as the topic icon.
95+ // An empty string removes the icon (EditForumTopic only).
96+ func WithForumTopicIconCustomEmojiID (id string ) ForumTopicOption {
97+ return func (c * forumTopicConfig ) {
98+ c .iconEmoji = id
99+ c .iconEmojiSet = true
100+ }
101+ }
102+
103+ // parseCustomEmojiID turns a Bot API custom_emoji_id string into the MTProto
104+ // document id. An empty string maps to 0 (no/removed icon).
105+ func parseCustomEmojiID (id string ) (int64 , error ) {
106+ if id == "" {
107+ return 0 , nil
108+ }
109+
110+ v , err := strconv .ParseInt (id , 10 , 64 )
111+ if err != nil {
112+ return 0 , & Error {Code : 400 , Description : "Bad Request: invalid icon_custom_emoji_id" }
113+ }
114+
115+ return v , nil
116+ }
117+
118+ // CreateForumTopic creates a topic in a forum supergroup. The bot must be an
119+ // administrator with the can_manage_topics right. Returns the created topic.
120+ func (b * Bot ) CreateForumTopic (ctx context.Context , chat ChatID , name string , opts ... ForumTopicOption ) (* ForumTopic , error ) {
121+ var cfg forumTopicConfig
122+
123+ for _ , o := range opts {
124+ o (& cfg )
125+ }
126+
127+ peer , err := b .resolveInputPeer (ctx , chat )
128+ if err != nil {
129+ return nil , err
130+ }
131+
132+ randomID , err := randInt64 ()
133+ if err != nil {
134+ return nil , err
135+ }
136+
137+ req := & tg.MessagesCreateForumTopicRequest {
138+ Peer : peer ,
139+ Title : name ,
140+ RandomID : randomID ,
141+ }
142+
143+ if cfg .iconColorSet {
144+ req .SetIconColor (cfg .iconColor )
145+ }
146+
147+ if cfg .iconEmojiSet {
148+ emojiID , err := parseCustomEmojiID (cfg .iconEmoji )
149+ if err != nil {
150+ return nil , err
151+ }
152+
153+ req .SetIconEmojiID (emojiID )
154+ }
155+
156+ resp , err := b .raw .MessagesCreateForumTopic (ctx , req )
157+ if err != nil {
158+ return nil , asAPIError (err )
159+ }
160+
161+ // The topic id is the id of the service message that created it; its
162+ // MessageActionTopicCreate carries the resolved name/color/icon.
163+ topic := & ForumTopic {Name : name , IconColor : cfg .iconColor , IconCustomEmojiID : cfg .iconEmoji }
164+
165+ if svc , ok := topicCreateService (resp ); ok {
166+ topic .MessageThreadID = svc .ID
167+
168+ if act , ok := svc .Action .(* tg.MessageActionTopicCreate ); ok {
169+ topic .Name = act .Title
170+ topic .IconColor = act .IconColor
171+
172+ if act .IconEmojiID != 0 {
173+ topic .IconCustomEmojiID = strconv .FormatInt (act .IconEmojiID , 10 )
174+ }
175+ }
176+ }
177+
178+ return topic , nil
179+ }
180+
181+ // editForumTopic is the shared implementation behind the (re)naming, icon,
182+ // close/reopen and hide/unhide operations on a topic.
183+ func (b * Bot ) editForumTopic (ctx context.Context , chat ChatID , topicID int , apply func (* tg.MessagesEditForumTopicRequest ) error ) error {
184+ peer , err := b .resolveInputPeer (ctx , chat )
185+ if err != nil {
186+ return err
187+ }
188+
189+ req := & tg.MessagesEditForumTopicRequest {Peer : peer , TopicID : topicID }
190+
191+ if err := apply (req ); err != nil {
192+ return err
193+ }
194+
195+ if _ , err := b .raw .MessagesEditForumTopic (ctx , req ); err != nil {
196+ return asAPIError (err )
197+ }
198+
199+ return nil
200+ }
201+
202+ // EditForumTopic edits the name and/or icon of a topic in a forum supergroup. The
203+ // bot must be an administrator with the can_manage_topics right.
204+ func (b * Bot ) EditForumTopic (ctx context.Context , chat ChatID , messageThreadID int , opts ... ForumTopicOption ) error {
205+ var cfg forumTopicConfig
206+
207+ for _ , o := range opts {
208+ o (& cfg )
209+ }
210+
211+ return b .editForumTopic (ctx , chat , messageThreadID , func (req * tg.MessagesEditForumTopicRequest ) error {
212+ if cfg .nameSet {
213+ req .SetTitle (cfg .name )
214+ }
215+
216+ if cfg .iconEmojiSet {
217+ emojiID , err := parseCustomEmojiID (cfg .iconEmoji )
218+ if err != nil {
219+ return err
220+ }
221+
222+ req .SetIconEmojiID (emojiID )
223+ }
224+
225+ return nil
226+ })
227+ }
228+
229+ // CloseForumTopic closes an open topic in a forum supergroup.
230+ func (b * Bot ) CloseForumTopic (ctx context.Context , chat ChatID , messageThreadID int ) error {
231+ return b .editForumTopic (ctx , chat , messageThreadID , func (req * tg.MessagesEditForumTopicRequest ) error {
232+ req .SetClosed (true )
233+
234+ return nil
235+ })
236+ }
237+
238+ // ReopenForumTopic reopens a closed topic in a forum supergroup.
239+ func (b * Bot ) ReopenForumTopic (ctx context.Context , chat ChatID , messageThreadID int ) error {
240+ return b .editForumTopic (ctx , chat , messageThreadID , func (req * tg.MessagesEditForumTopicRequest ) error {
241+ req .SetClosed (false )
242+
243+ return nil
244+ })
245+ }
246+
247+ // DeleteForumTopic deletes a topic in a forum supergroup together with all its
248+ // messages. The bot must be an administrator with the can_delete_messages right.
249+ func (b * Bot ) DeleteForumTopic (ctx context.Context , chat ChatID , messageThreadID int ) error {
250+ peer , err := b .resolveInputPeer (ctx , chat )
251+ if err != nil {
252+ return err
253+ }
254+
255+ if _ , err := b .raw .MessagesDeleteTopicHistory (ctx , & tg.MessagesDeleteTopicHistoryRequest {
256+ Peer : peer ,
257+ TopMsgID : messageThreadID ,
258+ }); err != nil {
259+ return asAPIError (err )
260+ }
261+
262+ return nil
263+ }
264+
265+ // UnpinAllForumTopicMessages clears the list of pinned messages in a forum topic.
266+ func (b * Bot ) UnpinAllForumTopicMessages (ctx context.Context , chat ChatID , messageThreadID int ) error {
267+ return b .unpinAllTopicMessages (ctx , chat , messageThreadID )
268+ }
269+
270+ // unpinAllTopicMessages unpins every message in the given topic.
271+ func (b * Bot ) unpinAllTopicMessages (ctx context.Context , chat ChatID , topicID int ) error {
272+ peer , err := b .resolveInputPeer (ctx , chat )
273+ if err != nil {
274+ return err
275+ }
276+
277+ req := & tg.MessagesUnpinAllMessagesRequest {Peer : peer }
278+ req .SetTopMsgID (topicID )
279+
280+ if _ , err := b .raw .MessagesUnpinAllMessages (ctx , req ); err != nil {
281+ return asAPIError (err )
282+ }
283+
284+ return nil
285+ }
286+
287+ // EditGeneralForumTopic edits the name of the "General" topic in a forum
288+ // supergroup. The bot must be an administrator with the can_manage_topics right.
289+ func (b * Bot ) EditGeneralForumTopic (ctx context.Context , chat ChatID , name string ) error {
290+ return b .editForumTopic (ctx , chat , generalForumTopicID , func (req * tg.MessagesEditForumTopicRequest ) error {
291+ req .SetTitle (name )
292+
293+ return nil
294+ })
295+ }
296+
297+ // CloseGeneralForumTopic closes the "General" topic in a forum supergroup.
298+ func (b * Bot ) CloseGeneralForumTopic (ctx context.Context , chat ChatID ) error {
299+ return b .editForumTopic (ctx , chat , generalForumTopicID , func (req * tg.MessagesEditForumTopicRequest ) error {
300+ req .SetClosed (true )
301+
302+ return nil
303+ })
304+ }
305+
306+ // ReopenGeneralForumTopic reopens the "General" topic in a forum supergroup.
307+ func (b * Bot ) ReopenGeneralForumTopic (ctx context.Context , chat ChatID ) error {
308+ return b .editForumTopic (ctx , chat , generalForumTopicID , func (req * tg.MessagesEditForumTopicRequest ) error {
309+ req .SetClosed (false )
310+
311+ return nil
312+ })
313+ }
314+
315+ // HideGeneralForumTopic hides the "General" topic in a forum supergroup. The
316+ // topic is automatically closed if it was open.
317+ func (b * Bot ) HideGeneralForumTopic (ctx context.Context , chat ChatID ) error {
318+ return b .editForumTopic (ctx , chat , generalForumTopicID , func (req * tg.MessagesEditForumTopicRequest ) error {
319+ req .SetHidden (true )
320+
321+ return nil
322+ })
323+ }
324+
325+ // UnhideGeneralForumTopic unhides the "General" topic in a forum supergroup.
326+ func (b * Bot ) UnhideGeneralForumTopic (ctx context.Context , chat ChatID ) error {
327+ return b .editForumTopic (ctx , chat , generalForumTopicID , func (req * tg.MessagesEditForumTopicRequest ) error {
328+ req .SetHidden (false )
329+
330+ return nil
331+ })
332+ }
333+
334+ // UnpinAllGeneralForumTopicMessages clears the list of pinned messages in the
335+ // "General" topic of a forum supergroup.
336+ func (b * Bot ) UnpinAllGeneralForumTopicMessages (ctx context.Context , chat ChatID ) error {
337+ return b .unpinAllTopicMessages (ctx , chat , generalForumTopicID )
338+ }
339+
9340// GetForumTopicIconStickers returns custom emoji stickers that can be used as a
10341// forum topic icon by any user.
11342func (b * Bot ) GetForumTopicIconStickers (ctx context.Context ) ([]Sticker , error ) {
0 commit comments