-
Notifications
You must be signed in to change notification settings - Fork 11
/
main.js
275 lines (188 loc) Β· 9.13 KB
/
main.js
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
// ----------------
// modules
const storage = require('node-persist')
const TelegramBot = require('node-telegram-bot-api')
const commandLineArgs = require('command-line-args')
const markdownEscape = require('markdown-escape')
const marked = require('marked')
// ----------------
// marked renderer options for telegram HTML
const markedRenderer = new marked.Renderer()
markedRenderer.paragraph = (text) => `\n\n${text}`
markedRenderer.list = (text) => `\n${text}`
markedRenderer.listitem = (text) => `\n- ${text}`
// ----------------
// command line arguments
const DEFAULT_PERSISTENCE_PATH = './.persistence'
const optionDefinitions = [
{ name: 'api-token', alias: 't', type: String },
{ name: 'bot-username', alias: 'u', type: String },
{ name: 'persistence-path', alias: 'p', type: String }
]
const options = commandLineArgs(optionDefinitions)
if (!options['api-token']) throw new Error('β The --api-token (-t) option is required!')
if (!options['bot-username']) throw new Error('β The --bot-username (-u) option is required!')
if (!options['persistence-path']) console.warn(`β οΈ No --persistence-path (-p) defined, the default will be used: ${DEFAULT_PERSISTENCE_PATH}`)
// ----------------
// constants
// bot info
const API_TOKEN = options['api-token']
const BOT_USERNAME = options['bot-username']
const PERSISTENCE_PATH = options['persistence-path'] || DEFAULT_PERSISTENCE_PATH
const BOT_ID = +API_TOKEN.split(':')[0]
// built-in messages
const ERROR_MESSAGE_PREFIX = 'β οΈ *Beep, boop, error!*'
const DEFAULT_MSG_TEMPLATE = 'π Welcome to $groupname $firstname ($safeusername)! π'
const START_MSG = 'Add me to a group! :)'
const INTRO_MSG = `π Hi!
I will send a welcome message to everyone in this group from now on.
To customize the message, use the command \`/change_welcome_message <your new message>\`.
You can use the following templates:
- \`$$username\`: the new member's username (example: $username)
- \`$$safeusername\`: the username, but if it isn't defined by the user, the first name will be used instead (example: $safeusername)
- \`$$firstname\`: the new member's first name (example: $firstname)
- \`$$groupname\`: the group's name (example: $groupname)
IMPORTANT: \`$username\` could fail if the user hasn't defined a username, and when that happens the resulting string will be \`@undefined\`. Because of this, it is recommended to use \`$safeusername\` instead.
Keep in mind that *only* the user who introduced me to this group ($username) can execute this command.
This bot is free and open source and was created by @DaniGuardiola
Repo: https://github.com/daniguardiola/telegram-welcome-bot
Enjoy! π`
const HELP_MSG = `*Welcome message bot help*
I will send a welcome message to every new member of a group.
I will start as soon as I get added to a group.
To customize the message, use the command \`/change_welcome_message <your new message>\`.
You can use the following templates:
- \`$username\`: the new member's username (with the @ character)
- \`$safeusername\`: the username, but if it isn't defined by the user, the first name will be used instead
- \`$firstname\`: the new member's first name
- \`$groupname\`: the group's name
IMPORTANT: \`$username\` could fail if the user hasn't defined a username, and when that happens the resulting string will be \`@undefined\`. Because of this, it is recommended to use \`$safeusername\` instead.
Keep in mind that *only* the user who introduces me to a group can execute this command.
This bot is free and open source and was created by @DaniGuardiola
Repo: https://github.com/daniguardiola/telegram-welcome-bot
Enjoy! π`
// ----------------
// bot instance
const bot = new TelegramBot(API_TOKEN, { polling: true })
// ----------------
// message composers
const composeMessage = (msg, member, groupname) => msg
.replace(/([^$])(\$username)/g, (full, pre) => `${pre}@${markdownEscape(member.username)}`)
.replace(/([^$])(\$firstname)/g, (full, pre) => `${pre}${markdownEscape(member.first_name)}`)
.replace(/([^$])(\$groupname)/g, (full, pre) => `${pre}${markdownEscape(groupname)}`)
.replace(/([^$])(\$safeusername)/g, (full, pre) => member.username
? `${pre}@${markdownEscape(member.username)}`
: `${pre}@${markdownEscape(member.first_name)}`)
// remove extra "$" in $$username / $$firstname / $$groupname
.replace(/\$\$(username|safeusername|firstname|groupname)/g, (full, match) => `$${markdownEscape(match)}`)
const composeIntroMessage = (owner, groupName) => composeMessage(INTRO_MSG, owner, groupName)
const composeErrorMessage = msg => `${ERROR_MESSAGE_PREFIX} ${msg}`
// ----------------
// helpers
const sendMessage = (chatId, msg) => bot.sendMessage(chatId, marked(msg, { renderer: markedRenderer }), { parse_mode: 'HTML' })
const sendErrorMessage = (chatId, msg) => sendMessage(chatId, composeErrorMessage(msg))
const isGroup = msg => ['group', 'supergroup'].indexOf(msg.chat.type) > -1
// ----------------
// data
// message template
const msgTemplates = {}
const getMsgTemplateKey = chatId => `${chatId}_msg_template`
const getMsgTemplate = async chatId => {
// already loaded in memory
if (msgTemplates[chatId]) return msgTemplates[chatId]
// persisted
const storedTemplate = await storage.getItem(getMsgTemplateKey(chatId))
if (storedTemplate) {
msgTemplates[chatId] = storedTemplate // load in memory
return storedTemplate
}
// not initialized
msgTemplates[chatId] = DEFAULT_MSG_TEMPLATE
return DEFAULT_MSG_TEMPLATE
}
const setMsgTemplate = async (chatId, msgTemplate) => {
await storage.setItem(getMsgTemplateKey(chatId), msgTemplate)
msgTemplates[chatId] = msgTemplate
}
const removeMsgTemplate = async chatId => {
delete msgTemplates[chatId]
await storage.removeItem(`${chatId}_msg_template`)
}
// owner id
const getOwnerIdKey = chatId => `${chatId}_owner_id`
const getOwnerId = chatId => storage.getItem(getOwnerIdKey(chatId))
const setOwnerId = (chatId, ownerId) => storage.setItem(getOwnerIdKey(chatId), ownerId)
const removeOwnerId = chatId => storage.removeItem(getOwnerIdKey(chatId))
// ----------------
// handlers
const notGroupHandler = async msg => sendMessage(msg.chat.id, '*Add me to a group first!*')
const newMemberHandler = async msg => {
const chatId = msg.chat.id
const groupName = msg.chat.title
const newMember = msg.new_chat_members[0]
if (newMember.id === BOT_ID) {
await sendMessage(chatId, composeIntroMessage(msg.from, groupName))
return setOwnerId(chatId, msg.from.id)
} else if (!newMember.is_bot) return sendMessage(chatId, composeMessage(await getMsgTemplate(chatId), newMember, groupName))
}
const memberLeftHandler = async msg => {
const chatId = msg.chat.id
const member = msg.left_chat_member // oh, I 'member!
if (member.id === BOT_ID) {
await removeOwnerId(chatId)
await removeMsgTemplate(chatId)
}
}
const changeWelcomeMessageHandler = async (msg, match) => {
if (!isGroup(msg)) return notGroupHandler(msg)
const chatId = msg.chat.id
const groupName = msg.chat.title
const owner = msg.from
const ownerId = await getOwnerId(chatId)
if (owner.id !== ownerId) return sendErrorMessage(chatId, 'Only the user who introduced me to this group can change the message!')
const msgTemplate = match[1].trim()
if (!msgTemplate.length) return changeWelcomeMessageEmptyHandler(msg)
await setMsgTemplate(chatId, msgTemplate)
const exampleMessage = composeMessage(msgTemplate, owner, groupName)
return sendMessage(chatId, `βοΈ New welcome message set! Here's an example:\n\n${exampleMessage}`)
}
const changeWelcomeMessageEmptyHandler = msg => {
if (!isGroup(msg)) return notGroupHandler(msg)
return sendErrorMessage(msg.chat.id, `You can't set an empty message!`)
}
const helpHandler = msg => {
// don't send the message if only the /help command is used
// on a group without the bot's mention appended
const mentionRegExp = new RegExp(`\\/help@${BOT_USERNAME}`)
const withMention = !!msg.text.match(mentionRegExp)
if (isGroup(msg) && !withMention) return
return sendMessage(msg.chat.id, HELP_MSG)
}
const startHandler = msg => {
// don't send the message on a group
if (isGroup(msg)) return
return sendMessage(msg.chat.id, START_MSG)
}
// ----------------
// execution
const run = async () => {
await storage.init({ dir: PERSISTENCE_PATH })
// send welcome message to new members (or intro message)
bot.on('new_chat_members', newMemberHandler)
// if bot leaves the group, remove its info
bot.on('left_chat_member', memberLeftHandler)
// change welcome message
const baseChangeMsgCommandRegExp = `\\/change_welcome_message(?:@${BOT_USERNAME})?`
// change the message on request
const changeMsgCommandRegExp = new RegExp(`${baseChangeMsgCommandRegExp}\\s([\\s\\S]+)`)
bot.onText(changeMsgCommandRegExp, changeWelcomeMessageHandler)
// detect empty messages in command
const changeMsgCommandRegExpEmpty = new RegExp(`${baseChangeMsgCommandRegExp}$`)
bot.onText(changeMsgCommandRegExpEmpty, changeWelcomeMessageEmptyHandler)
// display help message
const helpCommandRegExp = new RegExp(`\\/help(?:@${BOT_USERNAME})?`)
bot.onText(helpCommandRegExp, helpHandler)
// answer to start command
bot.onText(/\/start/, startHandler)
}
run()