iimplore

bot.py

Jan 29th, 2022 (edited)
192
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.65 KB | None | 0 0
  1. """
  2. Чтобы всё работало, нужно создать бота и добавить его администратором в канал
  3.  
  4. requirements.txt:
  5. pyTelegramBotAPI==4.4.0
  6. requests==2.26.0
  7.  
  8. Имя файла параметров передается первым аргументом при запуске скрипта
  9. Формат файла:
  10. {
  11.    "token": "",
  12.    "channel": -1,
  13.    "channel_link": "https://t.me/durov/",
  14.    "posts_file": "posts.db",
  15.    "board": "fa",
  16.    "thread_num": "",
  17.    "thread_subject": "^часотред *[N#№НH]* *[0-9]+",
  18.    "sleep_message": 3,
  19.    "sleep_thread": 20,
  20.    "sleep_error": 60,
  21.    "check_thread": 1800
  22. }
  23.  
  24. posts_file: файл базы данных SQLite, содержит одну таблицу, создастся при первом запуске, если не существует:
  25. thread_num/thread_subject взаимоисключающие
  26. Если thread_num заполнен, грузим только этот тред
  27. Если не заполнен, ищем по thread_subject последний тред на доске (синтаксис регулярных выражений)
  28. """
  29.  
  30. import json
  31. import requests
  32. import re
  33. import sqlite3
  34. import sys
  35. import telebot
  36. from io import BytesIO
  37. from time import sleep
  38. from datetime import datetime, timedelta
  39.  
  40. tags = (
  41.     ('<u>', '</u>'),
  42.     ('<s>', '</s>'),
  43.     ('<tg-spoiler>', '</tg-spoiler>'),
  44.     ('<code>', '</code>'),
  45.     ('<i>', '</i>'),
  46.     ('<b>', '</b>'),
  47. )
  48.  
  49.  
  50. def smart_split(comment, first, others):
  51.     """
  52.    comment: пост
  53.    first: длина первого сообщения
  54.    others: длина остальных сообщений
  55.    Возвращается str первого сообщения и list[str] остальных
  56.    Разбиваем построчно, потом слепляем на куски <= предельного размера
  57.    Первый кусок размера first, остальные размера others
  58.    Если разбили внутри тега, закрываем его в конце куска и открываем в начале следующего
  59.    """
  60.     if len(comment) <= first:
  61.         return comment, []
  62.  
  63.     comment_s = comment.split('\n')
  64.  
  65.     result = []
  66.     tmp = ''
  67.     max_len = first
  68.     for row in comment_s:
  69.         if len(tmp + row) < max_len:
  70.             tmp = tmp + '\n' + row
  71.         else:
  72.             result.append(tmp)
  73.             tmp = row
  74.             max_len = others
  75.     result.append(tmp)
  76.  
  77.     for i in range(len(result)):
  78.         for left, right in tags:
  79.             left_count = len(re.findall(left, result[i]))
  80.             right_count = len(re.findall(right, result[i]))
  81.             if left_count > right_count:
  82.                 result[i] = result[i] + right
  83.             elif right_count > left_count:
  84.                 result[i] = left + result[i]
  85.  
  86.     return result[0], result[1:]
  87.  
  88.  
  89. params = json.load(open(sys.argv[1], encoding='utf-8'))
  90. token = params['token']
  91. channel = params['channel']
  92. channel_link = params['channel_link']
  93. posts_file = params['posts_file']
  94. board = params['board']
  95. thread_num = params['thread_num']
  96. thread_subject = params['thread_subject']
  97. m = params['sleep_message']
  98. t = params['sleep_thread']
  99. e = params['sleep_error']
  100. check_interval = timedelta(seconds=params['check_thread'])
  101.  
  102. bot = telebot.TeleBot(token)
  103. thread_curr = thread_num
  104. last_check = datetime(2021, 1, 1)
  105. error_cnt = 0
  106.  
  107. con = sqlite3.connect(posts_file)
  108. cur = con.cursor()
  109. cur.execute("""
  110. create table if not exists post(post_num integer primary key,
  111.                                message_id text,
  112.                                create_date date default(datetime('now', 'localtime')))
  113. """)
  114.  
  115. while True:
  116.  
  117.     if not thread_num and datetime.now() - last_check > check_interval:
  118.         try:
  119.             resp = requests.get(f'http://2ch.hk/{board}/catalog_num.json')
  120.             j = resp.json()
  121.         except Exception as ex:
  122.             print(datetime.now(), 'catalog_num', ex)
  123.             sleep(e)
  124.             continue
  125.         thread_curr = [t['num'] for t in j["threads"] if re.search(thread_subject, t['subject'], flags=re.I)][0]
  126.         last_check = datetime.now()
  127.  
  128.     if cur.execute(f"select 1 from post where post_num = {thread_curr}").fetchone():
  129.         last_post = cur.execute(f"select max(post_num) + 1 from post").fetchone()[0]
  130.     else:
  131.         last_post = thread_curr
  132.         try:
  133.             bot.unpin_all_chat_messages(channel)
  134.             message = bot.send_message(channel,
  135.                                        f'Тред: https://2ch.hk/{board}/res/{thread_curr}.html',
  136.                                        disable_web_page_preview=True,
  137.                                        disable_notification=True)
  138.             bot.pin_chat_message(channel, message.id)
  139.         except Exception as ex:
  140.             print(datetime.now(), 'new_thread', ex)
  141.             sleep(e)
  142.             continue
  143.  
  144.     try:
  145.         # url = f'http://2ch.hk/makaba/mobile.fcgi?task=get_thread&board={board}&thread={thread_curr}&num={last_post}'
  146.         url = f'http://2ch.hk//api/mobile/v2/after/{board}/{thread_curr}/{last_post}'
  147.         resp = requests.get(url)
  148.         j = resp.json()
  149.     except Exception as ex:
  150.         print(datetime.now(), 'get_thread', ex)
  151.         sleep(e)
  152.         continue
  153.  
  154.     for post in j['posts']:
  155.  
  156.         comment = post['comment']
  157.         comment = comment.replace('&quot;', '"').replace('<br>', chr(10)).replace('&#47;', '/').replace('&gt;', '>')
  158.         comment = re.sub(r'<a href=.+?(>>[0-9]+)</a>', r'\1', comment)  # ответ на пост
  159.         comment = re.sub(r'<a href=.+?(>>[0-9]+ →)</a>', r'\1', comment)  # ответ на пост из другого треда
  160.         comment = re.sub(r'<a href=.+?(>>[0-9]+ \(OP\))</a>', r'\1', comment)  # ответ на оп пост
  161.         comment = re.sub(r'<a href=.+?>(.+?)\[РАСКРЫТЬ\]</a>', r'\1', comment)  # ссылка на ютуб
  162.         comment = re.sub(r'<a href=.+?>(.+?)</a>', r'\1', comment)  # просто ссылка
  163.         comment = re.sub(r'<span class="u">(.+?)</span>', r'<u>\1</u>', comment, flags=re.DOTALL)  # подчеркнуто
  164.         comment = re.sub(r'<span class="s">(.+?)</span>', r'<s>\1</s>', comment, flags=re.DOTALL)  # зачеркнуто
  165.         comment = re.sub(r'<span class="spoiler">(.+?)</span>', r'<tg-spoiler>\1</tg-spoiler>', comment,
  166.                          flags=re.DOTALL)  # спойлер
  167.         comment = re.sub(r'<span class="unkfunc">(.+?)</span>', r'<code>\1</code>', comment)  # гринтекст
  168.         comment = re.sub(r'<em>(.+?)</em>', r'<i>\1</i>', comment, flags=re.DOTALL)  # курсив
  169.         comment = re.sub(r'<strong>(.+?)</strong>', r'<b>\1</b>', comment, flags=re.DOTALL)  # жирный
  170.         comment = re.sub(r'<span style.+?>(.+?)</span>', r'\1', comment)  # цветной (нет аналога в телеге)
  171.         comment = re.sub(r'<sup>(.+?)</sup>', r'\1', comment, flags=re.DOTALL)  # над строкой (нет аналога в телеге)
  172.         comment = re.sub(r'<sub>(.+?)</sub>', r'\1', comment, flags=re.DOTALL)  # под строкой (нет аналога в телеге)
  173.  
  174.         # чистим остальные теги
  175.         comment = re.sub(r'<span class=".*?">(.+?)</span>', r'\1', comment, flags=re.DOTALL)
  176.         for i in set(re.findall(r'<.+?>', comment)) - set([t[0] for t in tags] + [t[1] for t in tags]):
  177.             comment = comment.replace(i, '')
  178.  
  179.         if post['subject']:
  180.             comment = '<b>' + post['subject'] + '</b>\n\n' + comment
  181.  
  182.         for reply in set(re.findall(r'(>>[0-9]+ \(OP\)|>>[0-9]+ →|>>[0-9]+)', comment)):
  183.             replied = re.search(r'[0-9]+', reply)[0]
  184.             replied_msg = cur.execute(f"select max(message_id) from post where post_num = {replied}").fetchone()[0]
  185.             if replied_msg:
  186.                 comment = comment.replace(reply, '<a href="' + channel_link + replied_msg + '">' + reply + '</a>')
  187.  
  188.         files = ['http://2ch.hk' + f['path'] for f in post['files']
  189.                  if f['size'] <= 10240 and f['height'] <= 6000 and f['width'] <= 6000
  190.                  and f['size'] > 0 and f['height'] > 0 and f['width'] > 0
  191.                  and f['height'] / f['width'] <= 20 and f['width'] / f['height'] <= 20
  192.                  and re.search(r'\.(jpg|jpeg|png|gif)$', f['path'].lower())
  193.                  or f['size'] <= 20480 and re.search(r'(\.mp4)$', f['path'].lower())] if post['files'] else []
  194.  
  195.         try:
  196.             if files:
  197.                 comment, tail = smart_split(comment, 1024, 4096)
  198.                 grp = []
  199.                 for i in range(len(files)):
  200.                     if files[i][-3:] in ('mp4'):  # 'gif', убрал временно
  201.                         grp.append(telebot.types.InputMediaVideo(files[i],
  202.                                                                  caption=comment if i == 0 else '',
  203.                                                                  parse_mode='HTML'))
  204.                     else:
  205.                         image_resp = requests.get(files[i])
  206.                         photo = telebot.types.InputMediaPhoto(media=BytesIO(image_resp.content),
  207.                                                               caption=comment if i == 0 else '',
  208.                                                               parse_mode='HTML')
  209.                         grp.append(photo)
  210.                 try:
  211.                     message = bot.send_media_group(channel, grp, disable_notification=True)
  212.                     message_id = str(message[0].id)
  213.                 except Exception as ex:
  214.                     print(datetime.now(), 'send_media', ex)
  215.                     message = bot.send_message(channel,
  216.                                                comment or chr(10060),
  217.                                                disable_web_page_preview=True,
  218.                                                disable_notification=True,
  219.                                                parse_mode='HTML')
  220.                     message_id = str(message.id)
  221.             else:
  222.                 comment, tail = smart_split(comment, 4096, 4096)
  223.                 message = bot.send_message(channel,
  224.                                            comment or chr(10060),
  225.                                            disable_web_page_preview=True,
  226.                                            disable_notification=True,
  227.                                            parse_mode='HTML')
  228.                 message_id = str(message.id)
  229.  
  230.         except Exception as ex:
  231.             print(datetime.now(), 'send_message', ex)
  232.             if error_cnt >= 3:
  233.                 message = bot.send_message(channel,
  234.                                            chr(10060),
  235.                                            disable_web_page_preview=True,
  236.                                            disable_notification=True,
  237.                                            parse_mode='HTML')
  238.                 message_id = str(message.id)
  239.             else:
  240.                 error_cnt += 1
  241.                 break
  242.  
  243.         cur.execute("insert into post(post_num, message_id) values(" + str(post['num']) + ", '" + message_id + "')")
  244.         con.commit()
  245.         error_cnt = 0
  246.  
  247.         for i in tail:
  248.             message = bot.send_message(channel, i,
  249.                                        reply_to_message_id=message_id,
  250.                                        disable_web_page_preview=True,
  251.                                        disable_notification=True,
  252.                                        parse_mode='HTML')
  253.             message_id = message.id
  254.  
  255.         sleep(m)
  256.  
  257.     sleep(t)
  258.  
Add Comment
Please, Sign In to add comment