-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
288 lines (251 loc) · 9.22 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
package main
import (
"fmt"
"strings"
"time"
"github.com/DazFather/parrbot/message"
"github.com/DazFather/parrbot/robot"
"github.com/DazFather/parrbot/tgui"
)
const (
// Default auto-reset of hashtags: every 24 hours
DEFAULT_RESET_TIME time.Duration = 24 * time.Hour
// Max. custom auto-reset of hashtags: 1 year
MAX_RESET_TIME = 8766 * time.Hour
// Min. custom auto-reset of hashtags: 1 hour
MIN_RESET_TIME = 1 * time.Hour
)
var (
// Map groupID -> ChatInfo
trending = map[int64]*ChatInfo{}
// Convert number between 0 and 10 into their emoji
number = [11]string{"0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"}
// Default page options
pageOpt = tgui.EditOptions{
DisableWebPagePreview: true,
ParseMode: "Markdown",
}
)
func main() {
robot.Start(
// Define the list of commands of the bot
messageHandler,
startHandler,
showHandler,
resetHandler,
helpHandler,
)
}
// Command handlers
var (
// Message hashtags extractor - extract and save hashtags sent in a group
messageHandler = robot.Command{
ReplyAt: message.MESSAGE,
CallFunc: func(bot *robot.Bot, update *message.Update) message.Any {
// Get the chatID of the current group chat
chatID := extractGroupID(update.Message)
if chatID == nil {
return nil
}
// Extract the hashtags from message and save them on ChatInfo of current group
if watcher := trending[*chatID]; watcher != nil {
watcher.Save(update.Message.ExtractEntitiesOfType("hashtag")...)
}
return nil
},
}
// Start command - welcome user in private chat or initialize current group if sent by admin
startHandler = robot.Command{
Description: "👤/👥 Start the bot",
Trigger: "/start",
ReplyAt: message.MESSAGE,
CallFunc: func(bot *robot.Bot, update *message.Update) message.Any {
// Get the chatID of the current group chat
var chatID *int64 = extractGroupID(update.Message)
// Private chat: Send welcome message
if update.Message.Chat.Type == "private" {
m := message.Text{
"👋 Welcome " + update.Message.From.FirstName + "!\n" +
"Add me to your group and send /start to keep it up to date with the most used hashtags",
nil,
}
return *m.ClipInlineKeyboard([][]tgui.InlineButton{{tgui.InlineCaller("🆘 Help & Info ℹ️", "/help")}})
}
// Group chat: start listening for hashtags
if !isFromAdmin(*update.Message) {
return nil
}
var timer time.Duration = DEFAULT_RESET_TIME
if payload := strings.Split(update.Message.Text, " "); len(payload) > 1 {
t, err := time.ParseDuration(strings.TrimSpace(payload[1]))
if err != nil {
return message.Text{"❌ Invalid time unit: wrong format, use something like this \"3h5m\" to indicate 3 hours and 5 minutes", nil}
}
if t > MAX_RESET_TIME {
return message.Text{"❌ Invalid time unit: duration too long, max avaiable is " + fmt.Sprint(MAX_RESET_TIME), nil}
} else if t < MIN_RESET_TIME && t != 0 {
return message.Text{"❌ Invalid time unit: duration too short, min avaiable is " + fmt.Sprint(MIN_RESET_TIME) + ". Use 0 to disable auto-reset", nil}
}
timer = t
}
watchGroup(*chatID, timer)
var text string = "Group setted!👌 Now I will start catching all the #hashtags for you\nAuto-reset: "
if timer != 0 {
text += "ON - every " + fmt.Sprint(timer)
} else {
text += "OFF - use /show and /reset to show or reset your hashtags"
}
return message.Text{text, nil}
},
}
// Shows trending hashtags - shows the 10 top trending hashtags in current group
showHandler = robot.Command{
Description: "👥 Show trending hashtags in current group",
Trigger: "/show",
ReplyAt: message.MESSAGE,
CallFunc: func(bot *robot.Bot, update *message.Update) (msg message.Any) {
// Get the chatID of the current group chat
chatID := extractGroupID(update.Message)
if chatID == nil {
return message.Text{"You are not in a group", nil}
}
// Check if sending user is authorized
if !isFromAdmin(*update.Message) {
return nil
}
// use the ChatInfo to build the message that display the top 10 trending hashtags
if groupTrends := trending[*chatID]; groupTrends != nil {
if m := buildTrendingMessage(*groupTrends); m != nil {
msg = *m
}
}
if msg == nil {
msg = message.Text{"No recent hashtag used in this group", nil}
}
return
},
}
// Reset the counter and disable auto-reset of hashtags
resetHandler = robot.Command{
Description: "👥 Reset saved hashtags and turn off auto-reset",
Trigger: "/reset",
ReplyAt: message.MESSAGE,
CallFunc: func(bot *robot.Bot, update *message.Update) message.Any {
// Get the chatID of the current group chat
chatID := extractGroupID(update.Message)
if chatID == nil {
return message.Text{"You are not in a group", nil}
}
// Check if sending user is authorized
if !isFromAdmin(*update.Message) {
return nil
}
// If ChatInfo for current group is available then stop auto reset
if watcher := trending[*chatID]; watcher != nil {
watcher.StopAutoReset()
return message.Text{"Counter has been resetted. Use /start to turn auto-reset on", nil}
}
return message.Text{"I'm not listening this group. Use /start to start catching", nil}
},
}
// Help menu - shows help pages (just on provate chat)
helpHandler robot.Command = genPageMenu(
"/help",
"👤 How to use and set-up the bot and other infos",
page{title: "Command list", caption: []string{
"👤 *Private commands*:",
"/start - Welcome message",
"/help - How to use the bot and it's info. What you are seeing right now",
"\n👥 *Group commands* (admin only):",
"/start - Start listening for hashtags on the current group and turn auto-reset on",
"/show - Shows the top 10 most popular hashtags for the current group",
"/reset - Reset the hashtag counter and turn off auto-reset for the current group",
}},
page{title: "What is auto-reset mode", caption: []string{
"This mode will cause the reset of all saved hashtags every regular time interval, counter will start when /start command is sent on group.",
"By default the time interval is of " + fmt.Sprint(DEFAULT_RESET_TIME) + ", but it can be specified when the command is sent. ex: \"/start 48h3m20s\"",
"If \"0\" is used as time interval auto-reset mode will not be active.",
"When auto-reset is on the bot will also show to the group the top 10 most used hashtags just before they reset.\n",
"Use /reset to turn off the auto-reset mode and delete all saved hashtags. Send /start to turn it on again",
}},
page{title: "Why use this bot", caption: []string{
"💸 *Free* - No payments required to use this bot. [Donations](https://paypal.me/DazFather) to the developer are still welcome",
"\n⏱ *Ready to go* - Just add this bot to a group to stay up-to-date with the trending hashtag.",
"\n🔒 *Privacy focused* - No log or referce to the sent message will be saved, there is no database and the [code is open](https://github.com/DazFather/hashtagCatcher/)",
}},
page{title: "Developer info", caption: []string{
"This bot is still work in progress and is being actively developed with ❤️ by @DazFather.",
"It is powerade by the [Parr(B)ot](https://github.com/DazFather/parrbot) framework and is written in [Go](https://go.dev/)",
"Feel free to contact me on Telegram or [contribute to the project](https://github.com/DazFather/hashtagCatcher/)",
}},
)
)
/* --- UTILITY --- */
func watchGroup(groupID int64, autoReset time.Duration) {
var info *ChatInfo = trending[groupID]
if info == nil {
info = new(ChatInfo)
trending[groupID] = info
}
if autoReset == 0 {
return
}
info.SetAutoReset(autoReset, func(info ChatInfo) {
if msg := buildTrendingMessage(info); msg != nil {
msg.Send(groupID)
}
})
}
func extractGroupID(msg *message.UpdateMessage) *int64 {
if msg == nil || msg.Chat == nil {
return nil
}
return &msg.Chat.ID
}
func isFromAdmin(msg message.UpdateMessage) bool {
if msg.Chat == nil || msg.From == nil || msg.Chat.ID == msg.From.ID {
return false
}
res, err := message.API().GetChatMember(msg.Chat.ID, msg.From.ID)
if err != nil {
return false
}
return res.Result.Status == "creator" || res.Result.Status == "administrator"
}
func buildTrendingMessage(info ChatInfo) *message.Text {
// Take the first 10 trending hashtags
var trend []string = info.Trending(10)
if trend == nil || len(trend) == 0 {
return nil
}
// Build the final message
msg := "🔥 Trending hashtag:\n\n"
for i, tag := range trend {
msg += fmt.Sprint(number[i+1], " ", tag, " - used: ", info.hashtags[tag], "\n")
}
return &message.Text{msg, nil}
}
type page struct {
title string
caption []string
}
func (p page) paginate(n, tot int) tgui.Page {
return tgui.StaticPage(
fmt.Sprint(
"*", strings.ToUpper(p.title), "*\n\n",
strings.Join(p.caption, "\n"),
"\n\n` -- page ", n, "/", tot, "` 📄",
),
&pageOpt,
)
}
func genPageMenu(trigger, description string, pages ...page) robot.Command {
var (
tot = len(pages)
menu = tgui.PagedMenu{Pages: make([]tgui.Page, len(pages))}
)
for i, p := range pages {
menu.Pages[i] = p.paginate(i+1, tot)
}
return tgui.UseMenu(&menu, trigger, description)
}