commands.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. import re
  2. from io import BytesIO
  3. from sys import executable
  4. from struct import pack
  5. from asyncio import create_subprocess_shell
  6. from asyncio.subprocess import PIPE
  7. from datetime import datetime
  8. from ujson import dumps
  9. from tortoise.exceptions import IntegrityError
  10. from telethon.errors import MessageEmptyError
  11. from telethon.utils import get_display_name, get_peer_id
  12. from aiofiles.os import (
  13. remove,
  14. path
  15. )
  16. from aiohttp import ClientSession
  17. from emoji import is_emoji
  18. from cairosvg import svg2png
  19. from actions import (
  20. find_action,
  21. create_action,
  22. delete_action,
  23. add_gif,
  24. add_sticker,
  25. add_admin,
  26. delete_admin,
  27. add_or_update_birthday,
  28. get_birthdays,
  29. add_server,
  30. delete_server,
  31. add_allowed,
  32. delete_allowed,
  33. list_servers,
  34. get_server_ip
  35. )
  36. from utils import (
  37. make_temporary_filename,
  38. make_cache_filename,
  39. parse_kind,
  40. get_user_name,
  41. calculate_age,
  42. unparse,
  43. remove_ansi_escapes
  44. )
  45. class Handler:
  46. def __init__(self, handler, is_restricted=False):
  47. self.handler = handler
  48. self.is_restricted = is_restricted
  49. async def newadmin_handler(bot, event, command):
  50. if command.argc < 1:
  51. await event.reply('Пожалуйста, укажите пользователя!')
  52. return
  53. try:
  54. target = await bot.get_entity(command.args[0])
  55. except ValueError:
  56. await event.reply('Недопустимое имя пользователя!')
  57. return
  58. try:
  59. await add_admin(target)
  60. except IntegrityError:
  61. await event.reply('Данный пользователь уже является администратором!')
  62. return
  63. await event.reply('Готово!~~')
  64. async def deladmin_handler(bot, event, command):
  65. if command.argc < 1:
  66. await event.reply('Пожалуйста, укажите пользователя!')
  67. return
  68. try:
  69. target = await bot.get_entity(command.args[0])
  70. except ValueError:
  71. await event.reply('Недопустимое имя пользователя!')
  72. return
  73. try:
  74. await delete_admin(target)
  75. except IndexError:
  76. await event.reply('Данный пользователь не является администратором!')
  77. return
  78. await event.reply('Готово!~~')
  79. async def newaction_handler(bot, event, command):
  80. if command.argc < 3:
  81. await event.reply('Пожалуйста, укажите тип, имя и шаблон действия!')
  82. return
  83. try:
  84. kind = parse_kind(command.args[0])
  85. except ValueError:
  86. await event.reply('Неверный тип действия!!!')
  87. return
  88. try:
  89. await create_action(command.args[1], ' '.join(command.args[2:]), kind)
  90. except SyntaxError:
  91. await event.reply('Недопустимое имя действия!!!')
  92. return
  93. except IntegrityError:
  94. await event.reply('Действие с таким названием уже существует!')
  95. return
  96. await event.reply('Действие создано!')
  97. async def delaction_handler(bot, event, command):
  98. if command.argc < 1:
  99. await event.reply('Пожалуйста, укажите имя действия!')
  100. return
  101. try:
  102. await delete_action(command.args[0])
  103. except SyntaxError:
  104. await event.reply('Недопустимое имя действия!!!')
  105. return
  106. except NameError:
  107. await event.reply('Действия с таким названием не существует!')
  108. return
  109. await event.reply('Действие удалено!')
  110. async def addgif_handler(bot, event, command):
  111. if command.argc < 1:
  112. await event.reply('Пожалуйста, укажите имя действия!')
  113. return
  114. gif = await event.get_reply_message()
  115. if not gif or not gif.gif:
  116. await event.reply('Пожалуйста, добавьте GIF!')
  117. return
  118. try:
  119. action = await find_action(command.args[0])
  120. await add_gif(action, gif.file.id)
  121. except SyntaxError:
  122. await event.reply('Недопустимое имя действия!!!')
  123. return
  124. except NameError:
  125. await event.reply('Нет такого действия!')
  126. return
  127. await event.reply('Готово!~~')
  128. async def addserver_handler(bot, event, command):
  129. if command.argc < 2:
  130. await event.reply('Пожалуйста, укажите имя и адрес сервера!')
  131. return
  132. try:
  133. await add_server(command.args[0], command.args[1])
  134. except SyntaxError:
  135. await event.reply('Недопустимое имя сервера!!')
  136. return
  137. except ValueError:
  138. await event.reply('Пожалуйста, введите корректный IPv4-/IPv6-адрес!')
  139. return
  140. except IntegrityError:
  141. await event.reply('Данный сервер уже был добавлен ранее!')
  142. return
  143. await event.reply('Готово!~~')
  144. async def delserver_handler(bot, event, command):
  145. if command.argc < 1:
  146. await event.reply('Пожалуйста, укажите имя сервера!')
  147. return
  148. try:
  149. await delete_server(command.args[0])
  150. except SyntaxError:
  151. await event.reply('Недопустимое имя сервера!!')
  152. return
  153. except IndexError:
  154. await event.reply('Сервер с таким именем не найден!')
  155. return
  156. await event.reply('Готово!~~')
  157. async def allow_handler(bot, event, command):
  158. try:
  159. await add_allowed(get_peer_id(event.peer_id))
  160. except IntegrityError:
  161. await event.reply('Данный чат уже добавлен в список разрешённых!')
  162. return
  163. await event.reply('Готово!~~')
  164. async def disallow_handler(bot, event, command):
  165. try:
  166. await delete_allowed(get_peer_id(event.peer_id))
  167. except IndexError:
  168. await event.reply('Данный чат не найден в списке разрешённых!!')
  169. return
  170. await event.reply('Готово!~~')
  171. # Very, very, VERY evil code...
  172. async def make_message_shot(bot, message):
  173. if message.sender is None:
  174. sender_id = message.peer_id.channel_id
  175. full_name = await bot.get_entity(sender_id)
  176. full_name = full_name.title
  177. else:
  178. sender_id = message.sender.id
  179. full_name = get_display_name(message.sender)
  180. output_path = make_temporary_filename('png')
  181. avatar_path = await make_cache_filename(sender_id, 'png')
  182. if not await path.isfile(avatar_path):
  183. await bot.download_profile_photo(sender_id, file=avatar_path, download_big=True)
  184. # TO-DO: make it better.
  185. mproc = await create_subprocess_shell(
  186. f'mogrify -format png {avatar_path}'
  187. )
  188. await mproc.communicate()
  189. if not await path.isfile(avatar_path):
  190. avatar_path = './resources/placeholder.png'
  191. data = bytes()
  192. data += pack('I', len(output_path))
  193. data += bytes(output_path, encoding='ASCII')
  194. data += pack('I', len(avatar_path))
  195. data += bytes(avatar_path, encoding='ASCII')
  196. username = bytes(full_name, encoding='UTF-8')
  197. data += pack('I', len(username))
  198. data += username
  199. data += pack('I', sender_id % 7)
  200. text = bytes(unparse(message.raw_text, [entity for entity, _ in message.get_entities_text()]), encoding='UTF-8')
  201. data += pack('I', len(text))
  202. data += text
  203. proc = await create_subprocess_shell(
  204. './makeshot/makeshot',
  205. stdin=PIPE
  206. )
  207. await proc.communicate(input=data)
  208. pproc = await create_subprocess_shell(
  209. f'pngcrush -reduce -ow {output_path}'
  210. )
  211. await pproc.communicate()
  212. return output_path
  213. async def save_handler(bot, event, command):
  214. message = await event.get_reply_message()
  215. if not message:
  216. await event.reply('Пожалуйста, укажите сообщение для сохранения!')
  217. return
  218. emoji = '⚡'
  219. if command.argc >= 1:
  220. emoji = command.args[0]
  221. if len(emoji) not in range(1, 6)\
  222. or not all(map(is_emoji, emoji)):
  223. await event.reply('Указан некорректный эмодзи!!!')
  224. return
  225. path = await make_message_shot(bot, message)
  226. try:
  227. file = await add_sticker(bot, path, emoji)
  228. await bot.send_file(
  229. message.peer_id,
  230. file=file,
  231. reply_to=message
  232. )
  233. finally:
  234. await remove(path)
  235. async def bday_handler(bot, event, command):
  236. if command.argc >= 1:
  237. try:
  238. date = datetime.strptime(' '.join(command.args), '%d.%m.%Y')
  239. except ValueError:
  240. await event.reply('Дата не может быть распознана. Пожалуйста, введите свой день рождения в следующем формате: 01.01.1970 (день, месяц, год).')
  241. return
  242. if date >= datetime.now():
  243. await event.reply('День рождения не может быть в будущем...')
  244. return
  245. if await add_or_update_birthday(get_peer_id(event.peer_id), event.sender, date):
  246. await event.reply('День рождения успешно добавлен!!!')
  247. else:
  248. await event.reply('День рождения успешно обновлён!!!')
  249. return
  250. birthdays = await get_birthdays(get_peer_id(event.peer_id))
  251. if not birthdays:
  252. await event.reply('Пока пусто...')
  253. return
  254. birthdays = map(lambda birthday: (birthday.user_id, calculate_age(birthday.date)), birthdays)
  255. birthdays = sorted(birthdays, key=lambda birthday: birthday[1].days_until)
  256. birthdays_list = ''
  257. for user_id, age in birthdays:
  258. try:
  259. entity = await bot.get_entity(user_id)
  260. except ValueError:
  261. await bot.get_participants(await event.get_chat())
  262. try:
  263. entity = await bot.get_entity(user_id)
  264. except ValueError:
  265. continue
  266. birthdays_list += f'{get_user_name(entity)} ❯ {age.age_now} ➔ {age.age} ❯ {age.date_string} ❯ {age.days_until}\n'
  267. await event.reply(f'Дни рождения:\n\n{birthdays_list}')
  268. async def vpn_handler(bot, event, command):
  269. if command.argc < 1:
  270. await event.reply(f'Пожалуйста, укажите имя сервера! Доступные сервера: {await list_servers()}.')
  271. return
  272. try:
  273. ip = await get_server_ip(command.args[0])
  274. except SyntaxError:
  275. await event.reply('Недопустимое имя сервера!!')
  276. return
  277. except IndexError:
  278. await event.reply('Сервер с таким именем не найден!')
  279. return
  280. if event.sender is None:
  281. sender_id = event.peer_id.channel_id
  282. else:
  283. sender_id = event.sender.id
  284. async with ClientSession() as session:
  285. try:
  286. async with session.post(
  287. f'http://{ip}:9217/api/obtain',
  288. data={'user_id': sender_id}
  289. ) as resp:
  290. data = await resp.json()
  291. profile = data['profile']
  292. except:
  293. await event.reply('Произошла ошибка при попытке обращения к API сервера… :(')
  294. return
  295. try:
  296. await bot.send_message(
  297. await bot.get_entity(sender_id),
  298. f'Ваш файл конфигурации WireGuard (сервер {command.args[0]}):\n\n```{profile}```'
  299. )
  300. except:
  301. await event.reply('Произошла ошибка при отправке файла конфигурации в Ваши личные сообщения… :с')
  302. return
  303. await event.reply('Готово!!~~ Файл конфигурации WireGuard отправлен в Ваши личные сообщения!')
  304. async def run_handler(bot, event, command):
  305. if command.argc < 1:
  306. async with ClientSession() as session:
  307. try:
  308. async with session.get(
  309. 'https://farlands.txlyre.website/langs'
  310. ) as resp:
  311. text = await resp.read()
  312. text = text.decode('UTF-8')
  313. await event.reply(f'Доступные языки:\n`{text}`')
  314. except:
  315. await event.reply('Произошла ошибка при попытке обращения к API… :(')
  316. return
  317. match = re.match(r'^(\w+)(?:\s|\n)((?:\n|.)*)$', command.args_string)
  318. if not match:
  319. await event.reply('Пожалуйста, не оставляйте ввод пустым!')
  320. return
  321. language_name, text = match.groups()
  322. text = text.replace('\xA0', ' ') # i hate telegram
  323. async with ClientSession() as session:
  324. try:
  325. async with session.post(
  326. f'https://farlands.txlyre.website/run/{language_name}',
  327. data=text
  328. ) as resp:
  329. if resp.status in (404, 500):
  330. info = await resp.json()
  331. await event.reply(f'Произошла ошибка при попытке обращения к API… :(\nОтвет API: {info["detail"]}')
  332. return
  333. elif resp.status != 200:
  334. await event.reply('Сервер API временно недоступен. Пожалуйста, попробуйте ещё раз чуть позже.')
  335. return
  336. text = await resp.read()
  337. text = text.decode('UTF-8')[:4096]
  338. except:
  339. await event.reply('Произошла ошибка при попытке обращения к API… :(')
  340. return
  341. text = remove_ansi_escapes(text).strip()
  342. text = filter(lambda c: (c in ' \t\n' or ord(c) >= 32) and ord(c) not in range(128, 159), text)
  343. text = ''.join(text)
  344. text = text.replace('`', '')
  345. if not text:
  346. await event.reply('<пусто>')
  347. return
  348. try:
  349. try:
  350. await event.reply(f'```\n{text}```')
  351. except ValueError:
  352. await event.reply(text, parse_mode=None)
  353. except MessageEmptyError:
  354. await event.reply('<Telegram не смог декодировать текст сообщения>')
  355. async def sylvy_handler(bot, event, command):
  356. if command.argc < 1:
  357. await event.reply('Пожалуйста, не оставляйте ввод пустым!')
  358. return
  359. async with ClientSession() as session:
  360. try:
  361. async with session.post(
  362. f'https://sylvy-engine.txlyre.website/api/compute',
  363. json={'program': command.args_string, 'stringify': True}
  364. ) as resp:
  365. data = await resp.json()
  366. if data['status'] != 'ok':
  367. await event.reply(f'Ошибка API Sylvy: {data["data"]["message"]}')
  368. return
  369. result = data['data']['result']
  370. except:
  371. await event.reply('Произошла ошибка при попытке обращения к API Sylvy… :(')
  372. return
  373. if result['status'] == 'TIMEOUT':
  374. await event.reply(f'Максимальное время исполнения истекло (более тридцати секунд)!!!')
  375. return
  376. elif result['status'] != 'SUCCESS':
  377. await event.reply(f'Ошибка исполнения!!!\n\n```{result["stdout"]}```')
  378. return
  379. if result['plots']:
  380. plots = []
  381. for plot in result['plots']:
  382. buffer = BytesIO()
  383. svg2png(
  384. bytestring=plot,
  385. write_to=buffer
  386. )
  387. plots.append(buffer.getvalue())
  388. await bot.send_file(
  389. event.peer_id,
  390. file=plots,
  391. reply_to=event
  392. )
  393. return
  394. text = ''
  395. if result['stdout']:
  396. text += result['stdout']
  397. text += '\n'
  398. text += result['output']
  399. text = map(lambda line: '| ' + line, text.split('\n'))
  400. text = '\n'.join(text)
  401. await event.reply(f'```\n{text}```')
  402. COMMANDS = {
  403. 'newadmin': Handler(newadmin_handler, is_restricted=True),
  404. 'deladmin': Handler(deladmin_handler, is_restricted=True),
  405. 'newaction': Handler(newaction_handler, is_restricted=True),
  406. 'delaction': Handler(delaction_handler, is_restricted=True),
  407. 'addgif': Handler(addgif_handler, is_restricted=True),
  408. 'addserver': Handler(addserver_handler, is_restricted=True),
  409. 'delserver': Handler(delserver_handler, is_restricted=True),
  410. 'allow': Handler(allow_handler, is_restricted=True),
  411. 'disallow': Handler(disallow_handler, is_restricted=True),
  412. 'save': Handler(save_handler),
  413. 'bday': Handler(bday_handler),
  414. 'vpn': Handler(vpn_handler),
  415. 'sylvy': Handler(sylvy_handler),
  416. 'run': Handler(run_handler),
  417. }