2022-04-28 04:14:59 +08:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2020-10-26 02:11:53 +08:00
|
|
|
|
import os
|
2022-04-28 04:14:59 +08:00
|
|
|
|
import sys
|
2020-10-26 16:08:50 +08:00
|
|
|
|
import re
|
2021-08-04 22:54:49 +08:00
|
|
|
|
import requests
|
2021-12-13 04:54:02 +08:00
|
|
|
|
import telegram
|
2022-04-28 04:14:59 +08:00
|
|
|
|
from loguru import logger as _logger
|
|
|
|
|
from typing import Optional, Union, Any
|
|
|
|
|
from telegram.ext import Updater, MessageHandler, filters, Dispatcher
|
2022-03-28 05:37:30 +08:00
|
|
|
|
from functools import partial
|
2022-04-28 04:14:59 +08:00
|
|
|
|
from threading import Thread
|
|
|
|
|
from time import sleep
|
2022-06-22 14:10:01 +08:00
|
|
|
|
from itertools import product as _product
|
|
|
|
|
from random import choice
|
2020-10-26 16:08:50 +08:00
|
|
|
|
|
2020-10-26 02:11:53 +08:00
|
|
|
|
Filters = filters.Filters
|
2022-06-22 14:10:01 +08:00
|
|
|
|
|
2022-10-21 22:27:45 +08:00
|
|
|
|
parser = re.compile(r'^(?P<slash>[\\/]_?\$?)'
|
2022-03-28 05:58:47 +08:00
|
|
|
|
r'(?P<predicate>([^\s\\]|\\.)*((?<=\S)\\)?)'
|
2022-03-28 05:37:30 +08:00
|
|
|
|
r'(\s+(?P<complement>.+))?$')
|
2022-06-22 21:27:20 +08:00
|
|
|
|
ouenParser = re.compile(r'^('
|
|
|
|
|
r'\\ .* /'
|
|
|
|
|
r'|'
|
|
|
|
|
r'\ .* /'
|
|
|
|
|
r'|'
|
|
|
|
|
r'(\\.*/\s*){2,}'
|
|
|
|
|
r'|'
|
|
|
|
|
r'(\.*/\s*){2,}}'
|
|
|
|
|
r'|'
|
|
|
|
|
r'[/\\\/]{2,}'
|
|
|
|
|
r')$')
|
|
|
|
|
pinParser = re.compile(r'^[\\/]_?pin$')
|
2022-06-22 14:10:01 +08:00
|
|
|
|
|
2022-03-28 05:58:47 +08:00
|
|
|
|
convertEscapes = partial(re.compile(r'\\(\s)').sub, r'\1')
|
2021-12-13 04:54:02 +08:00
|
|
|
|
htmlEscape = lambda s: s.replace("<", "<").replace(">", ">").replace("&", "&")
|
2021-12-28 22:41:32 +08:00
|
|
|
|
mentionParser = re.compile(r'@([a-zA-Z]\w{4,})')
|
2020-10-26 02:11:53 +08:00
|
|
|
|
|
2022-06-22 14:10:01 +08:00
|
|
|
|
product = lambda a, b: tuple(map(','.join, _product(a, b)))
|
|
|
|
|
|
2022-04-13 16:29:43 +08:00
|
|
|
|
PUNCTUATION_TAIL = '.,?!;:~(' \
|
|
|
|
|
'。,?!;:~('
|
2022-06-22 21:52:06 +08:00
|
|
|
|
VEGETABLE = {
|
|
|
|
|
'permission_denied':
|
2022-06-22 14:10:01 +08:00
|
|
|
|
product(
|
|
|
|
|
{'我太菜了', '我好菜', '我好菜啊', '我菜死了'},
|
|
|
|
|
{'pin 不了这条消息', '学不会怎么 pin 这条消息', '连 pin 都不被允许'}
|
|
|
|
|
)
|
|
|
|
|
+
|
|
|
|
|
product(
|
|
|
|
|
{'我学不会怎么 pin 这条消息', '这么简单的消息我都 pin 不了', '好想 pin 这条消息啊,但我做不到'},
|
2022-06-22 21:52:06 +08:00
|
|
|
|
{'需要浇浇', '怎么会有我这么菜的 bot', '我只能混吃等死', '我怎么会菜成这样'}
|
2022-06-22 14:10:01 +08:00
|
|
|
|
)
|
2022-06-22 21:52:06 +08:00
|
|
|
|
+
|
|
|
|
|
product(
|
|
|
|
|
{'这可要我怎么 pin 呀', '怎么才能 pin 这条消息呀', 'pin 不动呀,这可怎么办'},
|
|
|
|
|
{'拿大头针钉上吗', '找把锤子敲到柱子上吗', '触及知识盲区了都'}
|
|
|
|
|
),
|
|
|
|
|
'reject':
|
|
|
|
|
product(
|
|
|
|
|
{'我累了', '我好懒,又懒又菜', '我的 bot 生只要像这样躺着混日子就已经很幸福了'},
|
|
|
|
|
{'根本不想 pin 这条消息', '才不要 pin 这条消息', '还是另请高明吧', '一点 pin 的动力都没有'}
|
|
|
|
|
)
|
|
|
|
|
}
|
2022-04-13 16:29:43 +08:00
|
|
|
|
|
2020-10-26 02:11:53 +08:00
|
|
|
|
# Docker env
|
2022-04-28 04:14:59 +08:00
|
|
|
|
TOKENS = re.compile(r'[^a-zA-Z\-_\d:]+').split(os.environ.get('TOKEN', ''))
|
|
|
|
|
if not TOKENS:
|
|
|
|
|
raise ValueError('no any valid token found')
|
|
|
|
|
|
|
|
|
|
TELEGRAM_PROXY = os.environ.get('PROXY', '')
|
|
|
|
|
REQUEST_PROXIES = {'all': TELEGRAM_PROXY} if TELEGRAM_PROXY else None
|
2020-10-26 02:11:53 +08:00
|
|
|
|
|
2022-04-28 04:14:59 +08:00
|
|
|
|
_logger.remove()
|
|
|
|
|
_logger.add(sys.stderr,
|
|
|
|
|
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
|
|
|
|
|
"|<level>{level:^8}</level>"
|
|
|
|
|
"|<cyan>{extra[username]:^15}</cyan>"
|
|
|
|
|
"|<level>{message}</level>",
|
|
|
|
|
level="DEBUG")
|
|
|
|
|
|
|
|
|
|
_updaters: list[Updater] = []
|
2020-11-10 01:46:55 +08:00
|
|
|
|
|
2021-08-05 00:18:42 +08:00
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
class User:
|
|
|
|
|
def __init__(self, uid: Optional[int] = None, username: Optional[str] = None, name: Optional[str] = None):
|
|
|
|
|
if not (uid and name) and not username:
|
|
|
|
|
raise ValueError('invalid user')
|
|
|
|
|
self.name = name
|
|
|
|
|
self.uid = uid
|
|
|
|
|
self.username = username
|
|
|
|
|
if not self.name and self.username:
|
|
|
|
|
self.__get_user_by_username()
|
2021-08-05 00:18:42 +08:00
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
def __get_user_by_username(self):
|
2022-04-28 04:14:59 +08:00
|
|
|
|
r = requests.get(f'https://t.me/{self.username}', proxies=REQUEST_PROXIES)
|
2021-12-13 04:54:02 +08:00
|
|
|
|
self.name = re.search(r'(?<=<meta property="og:title" content=").*(?=")', r.text, re.IGNORECASE).group(0)
|
|
|
|
|
page_title = re.search(r'(?<=<title>).*(?=</title>)', r.text, re.IGNORECASE).group(0)
|
|
|
|
|
if page_title == self.name: # user does not exist
|
|
|
|
|
self.name = None
|
2021-08-05 00:18:42 +08:00
|
|
|
|
|
2021-12-13 15:18:12 +08:00
|
|
|
|
def mention(self, mention_self: bool = False, pure: bool = False) -> str:
|
2021-12-13 04:54:02 +08:00
|
|
|
|
if not self.name:
|
|
|
|
|
return f'@{self.username}'
|
2021-08-05 00:57:22 +08:00
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
mention_deep_link = (f'tg://resolve?domain={self.username}'
|
|
|
|
|
if (self.username and (not self.uid or self.uid < 0))
|
|
|
|
|
else f'tg://user?id={self.uid}')
|
2021-12-13 15:18:12 +08:00
|
|
|
|
name = self.name if not mention_self else "自己"
|
2022-04-13 16:29:43 +08:00
|
|
|
|
return f'<a href="{mention_deep_link}">{name}</a>' if not pure else name
|
2021-08-05 00:18:42 +08:00
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
return (
|
|
|
|
|
type(self) == type(other)
|
|
|
|
|
and (
|
|
|
|
|
((self.uid or other.uid) and self.uid == other.uid) or
|
|
|
|
|
((self.username or other.username) and self.username == other.username)
|
|
|
|
|
)
|
|
|
|
|
)
|
2020-11-10 01:46:55 +08:00
|
|
|
|
|
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
def get_user(msg: telegram.Message) -> User:
|
|
|
|
|
user = msg.sender_chat or msg.from_user
|
|
|
|
|
return User(name=user.full_name or user.title, uid=user.id, username=user.username)
|
2021-08-05 00:27:25 +08:00
|
|
|
|
|
|
|
|
|
|
2022-04-28 04:14:59 +08:00
|
|
|
|
def get_users(msg: telegram.Message) -> tuple[User, User]:
|
2021-12-13 04:54:02 +08:00
|
|
|
|
msg_from = msg
|
|
|
|
|
msg_rpl = msg.reply_to_message or msg_from
|
|
|
|
|
from_user, rpl_user = get_user(msg_from), get_user(msg_rpl)
|
|
|
|
|
return from_user, rpl_user
|
2020-10-26 02:11:53 +08:00
|
|
|
|
|
|
|
|
|
|
2022-05-25 10:58:50 +08:00
|
|
|
|
def parse_command(ctx: telegram.ext.CallbackContext) -> Optional[dict[str, Union[str, bool]]]:
|
2022-04-28 04:14:59 +08:00
|
|
|
|
match = ctx.match
|
2022-03-28 05:37:30 +08:00
|
|
|
|
parsed = match.groupdict()
|
|
|
|
|
predicate = parsed['predicate']
|
2022-05-25 10:58:50 +08:00
|
|
|
|
complement = parsed['complement']
|
|
|
|
|
if not predicate and complement:
|
|
|
|
|
return None # invalid command
|
|
|
|
|
|
2022-03-28 05:58:47 +08:00
|
|
|
|
omit_le = predicate.endswith('\\')
|
|
|
|
|
predicate = predicate[:-1] if omit_le else predicate
|
|
|
|
|
predicate = convertEscapes(predicate)
|
2022-04-28 04:14:59 +08:00
|
|
|
|
predicate = ctx.bot_data['delUsername'](predicate)
|
2022-03-28 05:37:30 +08:00
|
|
|
|
result = {'predicate': htmlEscape(predicate),
|
2022-05-25 10:58:50 +08:00
|
|
|
|
'complement': htmlEscape(complement or ''),
|
2022-03-28 05:37:30 +08:00
|
|
|
|
'slash': parsed['slash'],
|
2022-10-21 22:27:45 +08:00
|
|
|
|
'swap': parsed['slash'] not in ('/', '/$'),
|
2022-03-28 05:58:47 +08:00
|
|
|
|
'omit_le': omit_le}
|
2021-08-06 14:00:01 +08:00
|
|
|
|
return result
|
|
|
|
|
|
2021-08-09 16:18:44 +08:00
|
|
|
|
|
2022-04-13 16:29:43 +08:00
|
|
|
|
def get_tail(tail_char: str) -> str:
|
|
|
|
|
if tail_char in PUNCTUATION_TAIL:
|
|
|
|
|
return ''
|
|
|
|
|
halfwidth_mark = tail_char.isascii()
|
|
|
|
|
return '!' if halfwidth_mark else '!'
|
|
|
|
|
|
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
def get_text(user_from: User, user_rpl: User, command: dict):
|
2021-12-13 15:18:12 +08:00
|
|
|
|
rpl_self = user_from == user_rpl
|
2021-12-13 04:54:02 +08:00
|
|
|
|
mention_from = user_from.mention()
|
2021-12-13 15:18:12 +08:00
|
|
|
|
mention_rpl = user_rpl.mention(mention_self=rpl_self)
|
2022-03-28 05:58:47 +08:00
|
|
|
|
slash, predicate, complement, omit_le = \
|
|
|
|
|
command['slash'], command['predicate'], command['complement'], command['omit_le']
|
2021-12-13 15:18:12 +08:00
|
|
|
|
|
2022-03-28 05:37:30 +08:00
|
|
|
|
if predicate == '':
|
2022-10-21 22:27:45 +08:00
|
|
|
|
ret = '!' if not command['swap'] else '¡'
|
2022-03-28 05:37:30 +08:00
|
|
|
|
elif predicate == 'me':
|
2021-12-13 15:18:12 +08:00
|
|
|
|
ret = f"{mention_from}{bool(complement) * ' '}{complement}"
|
2022-04-13 16:29:43 +08:00
|
|
|
|
ret += get_tail((complement or user_from.mention(pure=True))[-1])
|
2021-12-13 15:18:12 +08:00
|
|
|
|
elif predicate == 'you':
|
|
|
|
|
ret = f"{mention_rpl}{bool(complement) * ' '}{complement}"
|
2022-04-13 16:29:43 +08:00
|
|
|
|
ret += get_tail((complement or user_rpl.mention(mention_self=rpl_self, pure=True))[-1])
|
2021-12-13 15:18:12 +08:00
|
|
|
|
elif complement:
|
|
|
|
|
ret = f"{mention_from} {predicate} {mention_rpl} {complement}"
|
2022-04-13 16:29:43 +08:00
|
|
|
|
ret += get_tail(complement[-1])
|
2020-10-26 16:08:50 +08:00
|
|
|
|
else:
|
2022-03-28 05:58:47 +08:00
|
|
|
|
ret = f"{mention_from} {predicate} "
|
|
|
|
|
ret += '了 ' if not omit_le else ''
|
|
|
|
|
ret += mention_rpl
|
2022-04-13 16:29:43 +08:00
|
|
|
|
ret += get_tail(mention_rpl[-1])
|
2021-12-13 15:18:12 +08:00
|
|
|
|
return ret
|
2020-10-26 16:08:50 +08:00
|
|
|
|
|
|
|
|
|
|
2022-04-28 04:14:59 +08:00
|
|
|
|
def reply(update: telegram.Update, ctx: telegram.ext.CallbackContext):
|
2022-05-25 10:58:50 +08:00
|
|
|
|
command = parse_command(ctx)
|
|
|
|
|
if not command:
|
|
|
|
|
return
|
|
|
|
|
|
2022-04-28 04:14:59 +08:00
|
|
|
|
logger = ctx.bot_data['logger']
|
|
|
|
|
logger.debug(str(update.to_dict()))
|
2021-12-13 04:54:02 +08:00
|
|
|
|
msg = update.effective_message
|
|
|
|
|
from_user, rpl_user = get_users(msg)
|
|
|
|
|
|
|
|
|
|
if from_user == rpl_user:
|
|
|
|
|
mention_match = mentionParser.search(command['predicate'])
|
|
|
|
|
if mention_match:
|
|
|
|
|
mention = mentionParser.search(msg.text).group(1)
|
|
|
|
|
rpl_user = User(username=mention)
|
|
|
|
|
command['predicate'] = command['predicate'][:mention_match.start()]
|
2021-12-28 22:41:32 +08:00
|
|
|
|
else:
|
|
|
|
|
mention_match = mentionParser.search(command['complement'])
|
|
|
|
|
if mention_match:
|
|
|
|
|
mention = mentionParser.search(msg.text).group(1)
|
|
|
|
|
rpl_user = User(username=mention)
|
|
|
|
|
complement = command['complement']
|
|
|
|
|
complement = complement[:mention_match.start()] + complement[mention_match.end():]
|
|
|
|
|
command['complement'] = complement.strip()
|
2021-12-13 04:54:02 +08:00
|
|
|
|
|
|
|
|
|
if command['swap'] and (not from_user == rpl_user):
|
2021-08-06 14:00:01 +08:00
|
|
|
|
(from_user, rpl_user) = (rpl_user, from_user)
|
2020-10-26 15:36:12 +08:00
|
|
|
|
|
2021-12-13 04:54:02 +08:00
|
|
|
|
text = get_text(from_user, rpl_user, command)
|
2022-04-28 04:14:59 +08:00
|
|
|
|
logger.info(text)
|
2020-10-26 02:11:53 +08:00
|
|
|
|
|
2022-03-18 01:34:09 +08:00
|
|
|
|
update.effective_message.reply_text('\u200e' + text, parse_mode='HTML')
|
2020-10-26 02:11:53 +08:00
|
|
|
|
|
|
|
|
|
|
2022-05-25 11:17:09 +08:00
|
|
|
|
def repeat(update: telegram.Update, ctx: telegram.ext.CallbackContext):
|
|
|
|
|
logger = ctx.bot_data['logger']
|
|
|
|
|
logger.debug(str(update.to_dict()))
|
|
|
|
|
|
|
|
|
|
chat = update.effective_chat
|
|
|
|
|
msg = update.effective_message
|
|
|
|
|
if chat.id < 0:
|
|
|
|
|
chat = ctx.bot.get_chat(chat.id)
|
|
|
|
|
|
|
|
|
|
logger.info(msg.text)
|
|
|
|
|
msg.copy(chat.id) if chat.has_protected_content else msg.forward(chat.id)
|
|
|
|
|
|
|
|
|
|
|
2022-06-22 14:10:01 +08:00
|
|
|
|
def pin(update: telegram.Update, ctx: telegram.ext.CallbackContext):
|
|
|
|
|
logger = ctx.bot_data['logger']
|
|
|
|
|
logger.debug(str(update.to_dict()))
|
|
|
|
|
|
|
|
|
|
msg = update.effective_message
|
2022-06-22 21:52:06 +08:00
|
|
|
|
msg_to_pin = msg.reply_to_message
|
|
|
|
|
if not msg_to_pin:
|
|
|
|
|
vegetable = f'{choice(VEGETABLE["reject"])} (Reply to a message to use the command)'
|
|
|
|
|
msg.reply_text(vegetable)
|
|
|
|
|
logger.warning(vegetable)
|
|
|
|
|
return
|
2022-06-22 14:10:01 +08:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
msg_to_pin.unpin()
|
|
|
|
|
msg_to_pin.pin(disable_notification=True)
|
|
|
|
|
logger.info(f'Pinned {msg_to_pin.text}')
|
|
|
|
|
except telegram.error.BadRequest as e:
|
2022-06-22 21:52:06 +08:00
|
|
|
|
vegetable = f'{choice(VEGETABLE["permission_denied"])} ({e})'
|
2022-06-22 14:10:01 +08:00
|
|
|
|
msg_to_pin.reply_text(vegetable)
|
|
|
|
|
logger.warning(vegetable)
|
|
|
|
|
|
|
|
|
|
|
2022-04-28 04:14:59 +08:00
|
|
|
|
def start(token: str):
|
|
|
|
|
updater = Updater(token=token, use_context=True, request_kwargs={'proxy_url': TELEGRAM_PROXY})
|
|
|
|
|
dp: Dispatcher = updater.dispatcher
|
2022-10-17 00:40:27 +08:00
|
|
|
|
dp.add_handler(MessageHandler(Filters.regex(pinParser) & ~Filters.update.edited_message, pin, run_async=True))
|
|
|
|
|
dp.add_handler(MessageHandler(Filters.regex(ouenParser) & ~Filters.update.edited_message, repeat, run_async=True))
|
|
|
|
|
dp.add_handler(MessageHandler(Filters.regex(parser) & ~Filters.update.edited_message, reply, run_async=True))
|
2022-04-28 04:14:59 +08:00
|
|
|
|
username = f'@{updater.bot.username}'
|
|
|
|
|
logger = _logger.bind(username=username)
|
|
|
|
|
dp.bot_data['delUsername'] = partial(re.compile(username, re.I).sub, '')
|
|
|
|
|
dp.bot_data['logger'] = logger
|
2021-08-05 11:47:04 +08:00
|
|
|
|
|
|
|
|
|
updater.start_polling()
|
2022-04-28 04:14:59 +08:00
|
|
|
|
logger.info('Started')
|
|
|
|
|
|
|
|
|
|
_updaters.append(updater)
|
|
|
|
|
# updater.idle()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
threads: list[Thread] = []
|
|
|
|
|
for token in TOKENS:
|
|
|
|
|
thread = Thread(target=start, args=(token,), daemon=True)
|
|
|
|
|
threads.append(thread)
|
|
|
|
|
thread.start()
|
|
|
|
|
for thread in threads:
|
|
|
|
|
thread.join()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
sleep(1)
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
threads_and_logger: list[tuple[Thread, Any]] = []
|
|
|
|
|
for updater in _updaters:
|
|
|
|
|
thread = Thread(target=updater.stop, daemon=True)
|
|
|
|
|
threads_and_logger.append((thread, updater.dispatcher.bot_data['logger']))
|
|
|
|
|
thread.start()
|
|
|
|
|
for thread, logger in threads_and_logger:
|
|
|
|
|
thread.join()
|
|
|
|
|
logger.info('Stopped')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|