Karma commited on
Commit
6d752f5
·
2 Parent(s): 9de9c9d f10725a

Merge pull request #21 from Infamous-Hydra/Infamous-Hydra-patch-1

Browse files
Files changed (1) hide show
  1. Mikobot/plugins/notes.py +629 -0
Mikobot/plugins/notes.py ADDED
@@ -0,0 +1,629 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # <============================================== IMPORTS =========================================================>
2
+ import ast
3
+ import random
4
+ import re
5
+ from io import BytesIO
6
+
7
+ from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message, Update
8
+ from telegram.constants import MessageLimit, ParseMode
9
+ from telegram.error import BadRequest
10
+ from telegram.ext import (
11
+ CallbackQueryHandler,
12
+ CommandHandler,
13
+ ContextTypes,
14
+ MessageHandler,
15
+ filters,
16
+ )
17
+ from telegram.helpers import escape_markdown, mention_markdown
18
+
19
+ import Database.sql.notes_sql as sql
20
+ from Mikobot import DRAGONS, LOGGER, MESSAGE_DUMP, SUPPORT_CHAT, dispatcher, function
21
+ from Mikobot.plugins.disable import DisableAbleCommandHandler
22
+ from Mikobot.plugins.helper_funcs.chat_status import check_admin, connection_status
23
+ from Mikobot.plugins.helper_funcs.misc import build_keyboard, revert_buttons
24
+ from Mikobot.plugins.helper_funcs.msg_types import get_note_type
25
+ from Mikobot.plugins.helper_funcs.string_handling import (
26
+ escape_invalid_curly_brackets,
27
+ markdown_to_html,
28
+ )
29
+
30
+ from .cust_filters import MessageHandlerChecker
31
+
32
+ # <=======================================================================================================>
33
+
34
+ FILE_MATCHER = re.compile(r"^###file_id(!photo)?###:(.*?)(?:\s|$)")
35
+ STICKER_MATCHER = re.compile(r"^###sticker(!photo)?###:")
36
+ BUTTON_MATCHER = re.compile(r"^###button(!photo)?###:(.*?)(?:\s|$)")
37
+ MYFILE_MATCHER = re.compile(r"^###file(!photo)?###:")
38
+ MYPHOTO_MATCHER = re.compile(r"^###photo(!photo)?###:")
39
+ MYAUDIO_MATCHER = re.compile(r"^###audio(!photo)?###:")
40
+ MYVOICE_MATCHER = re.compile(r"^###voice(!photo)?###:")
41
+ MYVIDEO_MATCHER = re.compile(r"^###video(!photo)?###:")
42
+ MYVIDEONOTE_MATCHER = re.compile(r"^###video_note(!photo)?###:")
43
+
44
+ ENUM_FUNC_MAP = {
45
+ sql.Types.TEXT.value: dispatcher.bot.send_message,
46
+ sql.Types.BUTTON_TEXT.value: dispatcher.bot.send_message,
47
+ sql.Types.STICKER.value: dispatcher.bot.send_sticker,
48
+ sql.Types.DOCUMENT.value: dispatcher.bot.send_document,
49
+ sql.Types.PHOTO.value: dispatcher.bot.send_photo,
50
+ sql.Types.AUDIO.value: dispatcher.bot.send_audio,
51
+ sql.Types.VOICE.value: dispatcher.bot.send_voice,
52
+ sql.Types.VIDEO.value: dispatcher.bot.send_video,
53
+ }
54
+
55
+
56
+ # <================================================ FUNCTION =======================================================>
57
+ async def get(
58
+ update: Update,
59
+ context: ContextTypes.DEFAULT_TYPE,
60
+ notename,
61
+ show_none=True,
62
+ no_format=False,
63
+ ):
64
+ bot = context.bot
65
+ chat_id = update.effective_message.chat.id
66
+ chat = update.effective_chat
67
+ note_chat_id = update.effective_chat.id
68
+ note = sql.get_note(note_chat_id, notename)
69
+ message = update.effective_message # type: Optional[Message]
70
+
71
+ if note:
72
+ if MessageHandlerChecker.check_user(update.effective_user.id):
73
+ return
74
+ # If we're replying to a message, reply to that message (unless it's an error)
75
+ if (
76
+ message.reply_to_message
77
+ and not message.reply_to_message.forum_topic_created
78
+ ):
79
+ reply_id = message.reply_to_message.message_id
80
+ else:
81
+ reply_id = message.message_id
82
+ if note.is_reply:
83
+ if MESSAGE_DUMP:
84
+ try:
85
+ await bot.forward_message(
86
+ chat_id=chat_id,
87
+ from_chat_id=MESSAGE_DUMP,
88
+ message_id=note.value,
89
+ )
90
+ except BadRequest as excp:
91
+ if excp.message == "Message to forward not found":
92
+ await message.reply_text(
93
+ "This message seems to have been lost - I'll remove it "
94
+ "from your notes list.",
95
+ )
96
+ sql.rm_note(note_chat_id, notename)
97
+ else:
98
+ raise
99
+ else:
100
+ try:
101
+ await bot.forward_message(
102
+ chat_id=chat_id,
103
+ from_chat_id=chat_id,
104
+ message_id=markdown_to_html(note.value),
105
+ )
106
+ except BadRequest as excp:
107
+ if excp.message == "Message to forward not found":
108
+ await message.reply_text(
109
+ "Looks like the original sender of this note has deleted "
110
+ "their message - sorry! Get your bot admin to start using a "
111
+ "message dump to avoid this. I'll remove this note from "
112
+ "your saved notes.",
113
+ )
114
+ sql.rm_note(note_chat_id, notename)
115
+ else:
116
+ raise
117
+ else:
118
+ VALID_NOTE_FORMATTERS = [
119
+ "first",
120
+ "last",
121
+ "fullname",
122
+ "username",
123
+ "id",
124
+ "chatname",
125
+ "mention",
126
+ ]
127
+ valid_format = escape_invalid_curly_brackets(
128
+ note.value,
129
+ VALID_NOTE_FORMATTERS,
130
+ )
131
+ if valid_format:
132
+ if not no_format:
133
+ if "%%%" in valid_format:
134
+ split = valid_format.split("%%%")
135
+ if all(split):
136
+ text = random.choice(split)
137
+ else:
138
+ text = valid_format
139
+ else:
140
+ text = valid_format
141
+ else:
142
+ text = valid_format
143
+ text = text.format(
144
+ first=escape_markdown(message.from_user.first_name),
145
+ last=escape_markdown(
146
+ message.from_user.last_name or message.from_user.first_name,
147
+ ),
148
+ fullname=escape_markdown(
149
+ " ".join(
150
+ [message.from_user.first_name, message.from_user.last_name]
151
+ if message.from_user.last_name
152
+ else [message.from_user.first_name],
153
+ ),
154
+ ),
155
+ username="@" + message.from_user.username
156
+ if message.from_user.username
157
+ else mention_markdown(
158
+ message.from_user.id,
159
+ message.from_user.first_name,
160
+ ),
161
+ mention=mention_markdown(
162
+ message.from_user.id,
163
+ message.from_user.first_name,
164
+ ),
165
+ chatname=escape_markdown(
166
+ message.chat.title
167
+ if message.chat.type != "private"
168
+ else message.from_user.first_name,
169
+ ),
170
+ id=message.from_user.id,
171
+ )
172
+ else:
173
+ text = ""
174
+
175
+ keyb = []
176
+ parseMode = ParseMode.HTML
177
+ buttons = sql.get_buttons(note_chat_id, notename)
178
+ if no_format:
179
+ parseMode = None
180
+ text += revert_buttons(buttons)
181
+ else:
182
+ keyb = build_keyboard(buttons)
183
+
184
+ keyboard = InlineKeyboardMarkup(keyb)
185
+
186
+ try:
187
+ if note.msgtype in (sql.Types.BUTTON_TEXT, sql.Types.TEXT):
188
+ await bot.send_message(
189
+ chat_id,
190
+ markdown_to_html(text),
191
+ reply_to_message_id=reply_id,
192
+ parse_mode=parseMode,
193
+ disable_web_page_preview=True,
194
+ reply_markup=keyboard,
195
+ message_thread_id=message.message_thread_id
196
+ if chat.is_forum
197
+ else None,
198
+ )
199
+ else:
200
+ await ENUM_FUNC_MAP[note.msgtype](
201
+ chat_id,
202
+ note.file,
203
+ caption=markdown_to_html(text),
204
+ reply_to_message_id=reply_id,
205
+ parse_mode=parseMode,
206
+ disable_web_page_preview=True,
207
+ reply_markup=keyboard,
208
+ message_thread_id=message.message_thread_id
209
+ if chat.is_forum
210
+ else None,
211
+ )
212
+
213
+ except BadRequest as excp:
214
+ if excp.message == "Entity_mention_user_invalid":
215
+ await message.reply_text(
216
+ "Looks like you tried to mention someone I've never seen before. If you really "
217
+ "want to mention them, forward one of their messages to me, and I'll be able "
218
+ "to tag them!",
219
+ )
220
+ elif FILE_MATCHER.match(note.value):
221
+ await message.reply_text(
222
+ "This note was an incorrectly imported file from another bot - I can't use "
223
+ "it. If you really need it, you'll have to save it again. In "
224
+ "the meantime, I'll remove it from your notes list.",
225
+ )
226
+ sql.rm_note(note_chat_id, notename)
227
+ else:
228
+ await message.reply_text(
229
+ "This note could not be sent, as it is incorrectly formatted. Ask in "
230
+ f"@{SUPPORT_CHAT} if you can't figure out why!",
231
+ )
232
+ LOGGER.exception(
233
+ "Could not parse message #%s in chat %s",
234
+ notename,
235
+ str(note_chat_id),
236
+ )
237
+ LOGGER.warning("Message was: %s", str(note.value))
238
+ return
239
+ elif show_none:
240
+ await message.reply_text("This note doesn't exist")
241
+
242
+
243
+ @connection_status
244
+ async def cmd_get(update: Update, context: ContextTypes.DEFAULT_TYPE):
245
+ bot, args = context.bot, context.args
246
+ if len(args) >= 2 and args[1].lower() == "noformat":
247
+ await get(update, context, args[0].lower(), show_none=True, no_format=True)
248
+ elif len(args) >= 1:
249
+ await get(update, context, args[0].lower(), show_none=True)
250
+ else:
251
+ await update.effective_message.reply_text("Get rekt")
252
+
253
+
254
+ @connection_status
255
+ async def hash_get(update: Update, context: ContextTypes.DEFAULT_TYPE):
256
+ message = update.effective_message.text
257
+ fst_word = message.split()[0]
258
+ no_hash = fst_word[1:].lower()
259
+ await get(update, context, no_hash, show_none=False)
260
+
261
+
262
+ @connection_status
263
+ async def slash_get(update: Update, context: ContextTypes.DEFAULT_TYPE):
264
+ message, chat_id = update.effective_message.text, update.effective_chat.id
265
+ no_slash = message[1:]
266
+ note_list = sql.get_all_chat_notes(chat_id)
267
+
268
+ try:
269
+ noteid = note_list[int(no_slash) - 1]
270
+ note_name = str(noteid).strip(">").split()[1]
271
+ await get(update, context, note_name, show_none=False)
272
+ except IndexError:
273
+ await update.effective_message.reply_text("Wrong Note ID 😾")
274
+
275
+
276
+ @connection_status
277
+ @check_admin(is_user=True)
278
+ async def save(update: Update, context: ContextTypes.DEFAULT_TYPE):
279
+ chat_id = update.effective_chat.id
280
+ msg = update.effective_message # type: Optional[Message]
281
+ if len(context.args) < 1:
282
+ await msg.reply_text("You should give the note a name.")
283
+ return
284
+
285
+ note_name, text, data_type, content, buttons = get_note_type(msg)
286
+ note_name = note_name.lower()
287
+ if data_type is None:
288
+ await msg.reply_text("Dude, there's no note content")
289
+ return
290
+
291
+ sql.add_note_to_db(
292
+ chat_id,
293
+ note_name,
294
+ text,
295
+ data_type,
296
+ buttons=buttons,
297
+ file=content,
298
+ )
299
+
300
+ await msg.reply_text(
301
+ f"Yas! Added `{note_name}`.\nGet it with /get `{note_name}`, or `#{note_name}`",
302
+ parse_mode=ParseMode.MARKDOWN,
303
+ )
304
+
305
+ if (
306
+ msg.reply_to_message
307
+ and msg.reply_to_message.from_user.is_bot
308
+ and not msg.reply_to_message.forum_topic_created
309
+ ):
310
+ if text:
311
+ await msg.reply_text(
312
+ "Seems like you're trying to save a message from a bot. Unfortunately, "
313
+ "bots can't forward bot messages, so I can't save the exact message. "
314
+ "\nI'll save all the text I can, but if you want more, you'll have to "
315
+ "forward the message yourself, and then save it.",
316
+ )
317
+ else:
318
+ await msg.reply_text(
319
+ "Bots are kinda handicapped by telegram, making it hard for bots to "
320
+ "interact with other bots, so I can't save this message "
321
+ "like I usually would - do you mind forwarding it and "
322
+ "then saving that new message? Thanks!",
323
+ )
324
+ return
325
+
326
+
327
+ @connection_status
328
+ @check_admin(is_user=True)
329
+ async def clear(update: Update, context: ContextTypes.DEFAULT_TYPE):
330
+ args = context.args
331
+ chat_id = update.effective_chat.id
332
+ if len(args) >= 1:
333
+ notename = args[0].lower()
334
+
335
+ if sql.rm_note(chat_id, notename):
336
+ await update.effective_message.reply_text("Successfully removed note.")
337
+ else:
338
+ await update.effective_message.reply_text(
339
+ "That's not a note in my database!"
340
+ )
341
+
342
+
343
+ async def clearall(update: Update, context: ContextTypes.DEFAULT_TYPE):
344
+ chat = update.effective_chat
345
+ user = update.effective_user
346
+ member = await chat.get_member(user.id)
347
+ if member.status != "creator" and user.id not in DRAGONS:
348
+ await update.effective_message.reply_text(
349
+ "Only the chat owner can clear all notes at once.",
350
+ )
351
+ else:
352
+ buttons = InlineKeyboardMarkup(
353
+ [
354
+ [
355
+ InlineKeyboardButton(
356
+ text="Delete all notes",
357
+ callback_data="notes_rmall",
358
+ ),
359
+ ],
360
+ [InlineKeyboardButton(text="Cancel", callback_data="notes_cancel")],
361
+ ],
362
+ )
363
+ await update.effective_message.reply_text(
364
+ f"Are you sure you would like to clear ALL notes in {chat.title}? This action cannot be undone.",
365
+ reply_markup=buttons,
366
+ parse_mode=ParseMode.MARKDOWN,
367
+ )
368
+
369
+
370
+ async def clearall_btn(update: Update, context: ContextTypes.DEFAULT_TYPE):
371
+ query = update.callback_query
372
+ chat = update.effective_chat
373
+ message = update.effective_message
374
+ member = await chat.get_member(query.from_user.id)
375
+ if query.data == "notes_rmall":
376
+ if member.status == "creator" or query.from_user.id in DRAGONS:
377
+ note_list = sql.get_all_chat_notes(chat.id)
378
+ try:
379
+ for notename in note_list:
380
+ note = notename.name.lower()
381
+ sql.rm_note(chat.id, note)
382
+ await message.edit_text("Deleted all notes.")
383
+ except BadRequest:
384
+ return
385
+
386
+ if member.status == "administrator":
387
+ await query.answer("Only owner of the chat can do this.")
388
+
389
+ if member.status == "member":
390
+ await query.answer("You need to be admin to do this.")
391
+ elif query.data == "notes_cancel":
392
+ if member.status == "creator" or query.from_user.id in DRAGONS:
393
+ await message.edit_text("Clearing of all notes has been cancelled.")
394
+ return
395
+ if member.status == "administrator":
396
+ await query.answer("Only owner of the chat can do this.")
397
+ if member.status == "member":
398
+ await query.answer("You need to be admin to do this.")
399
+
400
+
401
+ @connection_status
402
+ async def list_notes(update: Update, context: ContextTypes.DEFAULT_TYPE):
403
+ chat_id = update.effective_chat.id
404
+ note_list = sql.get_all_chat_notes(chat_id)
405
+ notes = len(note_list) + 1
406
+ msg = "Get note by `/notenumber` or `#notename` \n\n *ID* *Note* \n"
407
+ for note_id, note in zip(range(1, notes), note_list):
408
+ if note_id < 10:
409
+ note_name = f"`{note_id:2}.` `#{(note.name.lower())}`\n"
410
+ else:
411
+ note_name = f"`{note_id}.` `#{(note.name.lower())}`\n"
412
+ if len(msg) + len(note_name) > MessageLimit.MAX_TEXT_LENGTH:
413
+ await update.effective_message.reply_text(
414
+ msg, parse_mode=ParseMode.MARKDOWN
415
+ )
416
+ msg = ""
417
+ msg += note_name
418
+
419
+ if not note_list:
420
+ try:
421
+ await update.effective_message.reply_text("No notes in this chat!")
422
+ except BadRequest:
423
+ await update.effective_message.reply_text(
424
+ "No notes in this chat!", quote=False
425
+ )
426
+
427
+ elif len(msg) != 0:
428
+ await update.effective_message.reply_text(msg, parse_mode=ParseMode.MARKDOWN)
429
+
430
+
431
+ async def __import_data__(chat_id, data, message: Message):
432
+ failures = []
433
+ for notename, notedata in data.get("extra", {}).items():
434
+ match = FILE_MATCHER.match(notedata)
435
+ matchsticker = STICKER_MATCHER.match(notedata)
436
+ matchbtn = BUTTON_MATCHER.match(notedata)
437
+ matchfile = MYFILE_MATCHER.match(notedata)
438
+ matchphoto = MYPHOTO_MATCHER.match(notedata)
439
+ matchaudio = MYAUDIO_MATCHER.match(notedata)
440
+ matchvoice = MYVOICE_MATCHER.match(notedata)
441
+ matchvideo = MYVIDEO_MATCHER.match(notedata)
442
+ matchvn = MYVIDEONOTE_MATCHER.match(notedata)
443
+
444
+ if match:
445
+ failures.append(notename)
446
+ notedata = notedata[match.end() :].strip()
447
+ if notedata:
448
+ sql.add_note_to_db(chat_id, notename[1:], notedata, sql.Types.TEXT)
449
+ elif matchsticker:
450
+ content = notedata[matchsticker.end() :].strip()
451
+ if content:
452
+ sql.add_note_to_db(
453
+ chat_id,
454
+ notename[1:],
455
+ notedata,
456
+ sql.Types.STICKER,
457
+ file=content,
458
+ )
459
+ elif matchbtn:
460
+ parse = notedata[matchbtn.end() :].strip()
461
+ notedata = parse.split("<###button###>")[0]
462
+ buttons = parse.split("<###button###>")[1]
463
+ buttons = ast.literal_eval(buttons)
464
+ if buttons:
465
+ sql.add_note_to_db(
466
+ chat_id,
467
+ notename[1:],
468
+ notedata,
469
+ sql.Types.BUTTON_TEXT,
470
+ buttons=buttons,
471
+ )
472
+ elif matchfile:
473
+ file = notedata[matchfile.end() :].strip()
474
+ file = file.split("<###TYPESPLIT###>")
475
+ notedata = file[1]
476
+ content = file[0]
477
+ if content:
478
+ sql.add_note_to_db(
479
+ chat_id,
480
+ notename[1:],
481
+ notedata,
482
+ sql.Types.DOCUMENT,
483
+ file=content,
484
+ )
485
+ elif matchphoto:
486
+ photo = notedata[matchphoto.end() :].strip()
487
+ photo = photo.split("<###TYPESPLIT###>")
488
+ notedata = photo[1]
489
+ content = photo[0]
490
+ if content:
491
+ sql.add_note_to_db(
492
+ chat_id,
493
+ notename[1:],
494
+ notedata,
495
+ sql.Types.PHOTO,
496
+ file=content,
497
+ )
498
+ elif matchaudio:
499
+ audio = notedata[matchaudio.end() :].strip()
500
+ audio = audio.split("<###TYPESPLIT###>")
501
+ notedata = audio[1]
502
+ content = audio[0]
503
+ if content:
504
+ sql.add_note_to_db(
505
+ chat_id,
506
+ notename[1:],
507
+ notedata,
508
+ sql.Types.AUDIO,
509
+ file=content,
510
+ )
511
+ elif matchvoice:
512
+ voice = notedata[matchvoice.end() :].strip()
513
+ voice = voice.split("<###TYPESPLIT###>")
514
+ notedata = voice[1]
515
+ content = voice[0]
516
+ if content:
517
+ sql.add_note_to_db(
518
+ chat_id,
519
+ notename[1:],
520
+ notedata,
521
+ sql.Types.VOICE,
522
+ file=content,
523
+ )
524
+ elif matchvideo:
525
+ video = notedata[matchvideo.end() :].strip()
526
+ video = video.split("<###TYPESPLIT###>")
527
+ notedata = video[1]
528
+ content = video[0]
529
+ if content:
530
+ sql.add_note_to_db(
531
+ chat_id,
532
+ notename[1:],
533
+ notedata,
534
+ sql.Types.VIDEO,
535
+ file=content,
536
+ )
537
+ elif matchvn:
538
+ video_note = notedata[matchvn.end() :].strip()
539
+ video_note = video_note.split("<###TYPESPLIT###>")
540
+ notedata = video_note[1]
541
+ content = video_note[0]
542
+ if content:
543
+ sql.add_note_to_db(
544
+ chat_id,
545
+ notename[1:],
546
+ notedata,
547
+ sql.Types.VIDEO_NOTE,
548
+ file=content,
549
+ )
550
+ else:
551
+ sql.add_note_to_db(chat_id, notename[1:], notedata, sql.Types.TEXT)
552
+
553
+ if failures:
554
+ with BytesIO(str.encode("\n".join(failures))) as output:
555
+ output.name = "failed_imports.txt"
556
+ await dispatcher.bot.send_document(
557
+ chat_id,
558
+ document=output,
559
+ filename="failed_imports.txt",
560
+ caption="These files/photos failed to import due to originating "
561
+ "from another bot. This is a telegram API restriction, and can't "
562
+ "be avoided. Sorry for the inconvenience!",
563
+ message_thread_id=message.message_thread_id
564
+ if message.chat.is_forum
565
+ else None,
566
+ )
567
+
568
+
569
+ def __stats__():
570
+ return f"• {sql.num_notes()} notes, across {sql.num_chats()} chats."
571
+
572
+
573
+ def __migrate__(old_chat_id, new_chat_id):
574
+ sql.migrate_chat(old_chat_id, new_chat_id)
575
+
576
+
577
+ def __chat_settings__(chat_id, user_id):
578
+ notes = sql.get_all_chat_notes(chat_id)
579
+ return f"There are `{len(notes)}` notes in this chat."
580
+
581
+
582
+ # <=================================================== HELP ====================================================>
583
+
584
+
585
+ __help__ = """
586
+ » /get <notename> : get the note with this notename
587
+ » #<notename> : same as /get
588
+ » /notes or /saved : list all saved notes in this chat
589
+ » /number : Will pull the note of that number in the list
590
+ ➠ If you would like to retrieve the contents of a note without any formatting, use `/get <notename> noformat`. This can \
591
+ be useful when updating a current note
592
+
593
+ *Admins only:*
594
+ » /save <notename> <notedata> : saves notedata as a note with name notename
595
+ ➠ A button can be added to a note by using standard markdown link syntax - the link should just be prepended with a \
596
+ `buttonurl:` section, as such: `[somelink](buttonurl:example.com)`. Check `/markdownhelp` for more info
597
+ » /save <notename> : save the replied message as a note with name notename
598
+ Separate diff replies by `%%%` to get random notes
599
+ ➠ *Example:*
600
+ `/save notename
601
+ Reply 1
602
+ %%%
603
+ Reply 2
604
+ %%%
605
+ Reply 3`
606
+ » /clear <notename>: clear note with this name
607
+ » /removeallnotes: removes all notes from the group
608
+ ➠ *Note:* Note names are case-insensitive, and they are automatically converted to lowercase before getting saved.
609
+
610
+ """
611
+
612
+ __mod_name__ = "NOTES"
613
+
614
+ # <================================================ HANDLER =======================================================>
615
+ function(CommandHandler("get", cmd_get))
616
+ function(MessageHandler(filters.Regex(r"^#[^\s]+"), hash_get, block=False))
617
+ function(MessageHandler(filters.Regex(r"^/\d+$"), slash_get, block=False))
618
+ function(CommandHandler("save", save, block=False))
619
+ function(CommandHandler("clear", clear, block=False))
620
+
621
+ function(
622
+ DisableAbleCommandHandler(
623
+ ["notes", "saved"], list_notes, admin_ok=True, block=False
624
+ )
625
+ )
626
+
627
+ function(DisableAbleCommandHandler("removeallnotes", clearall, block=False))
628
+ function(CallbackQueryHandler(clearall_btn, pattern=r"notes_.*", block=False))
629
+ # <================================================ END =======================================================>