From 393ba738d38c057c04a4ac00693e0546deee3bf3 Mon Sep 17 00:00:00 2001
From: Rongrong <15956627+Rongronggg9@users.noreply.github.com>
Date: Mon, 13 Dec 2021 04:54:02 +0800
Subject: [PATCH] rewrite
---
.github/workflows/publish-docker-image.yml | 2 +-
Dockerfile | 2 +-
README.md | 13 +-
SlashBot.py | 156 ++++++++++-----------
4 files changed, 83 insertions(+), 90 deletions(-)
diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml
index 309db5c..58da732 100644
--- a/.github/workflows/publish-docker-image.yml
+++ b/.github/workflows/publish-docker-image.yml
@@ -21,5 +21,5 @@ jobs:
uses: docker/build-push-action@v2
with:
push: true
- platforms: linux/amd64,linux/arm64
+ platforms: linux/amd64,linux/386,linux/arm64
tags: ${{ secrets.DOCKER_USERNAME }}/slashbot:latest
diff --git a/Dockerfile b/Dockerfile
index cb4451c..61e6cf6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,6 +4,6 @@ WORKDIR /app
COPY . /app
-RUN pip install --trusted-host pypi.python.org -r /app/requirements.txt
+RUN pip install --no-cache-dir --trusted-host pypi.python.org -r /app/requirements.txt
CMD ["python", "-u", "SlashBot.py"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 2519efa..bbdcd6b 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,18 @@
# SlashBot
+
[![Docker Cloud Automated build](https://img.shields.io/docker/cloud/automated/rongronggg9/slashbot)](https://hub.docker.com/r/rongronggg9/slashbot)
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/rongronggg9/slashbot)](https://hub.docker.com/r/rongronggg9/slashbot)
[![Docker Pulls](https://img.shields.io/docker/pulls/rongronggg9/slashbot)](https://hub.docker.com/r/rongronggg9/slashbot)
[![GitHub stars](https://img.shields.io/github/stars/Rongronggg9/SlashBot?style=social)](https://github.com/Rongronggg9/SlashBot)
**[@RongSlashBot](https://t.me/RongSlashBot)**
-**Privacy Mode on,接收不到非指令消息,保证隐私安全性。**
+**Privacy Mode on,接收不到非指令消息,保证隐私安全性。**
由于 Telegram 限制,如您的群组已有其他 bot ,请使用 Privacy Mode off 的 [@RongSlashRBot](https://t.me/RongSlashRBot) 。
-请注意,Privacy Mode off 意味着 bot 在技术上可以收到所有消息,本 bot 已经设计为不处理及不记录任何无关消息,如您疑虑安全性,
-请自行搭建或到末尾查看更多解决方案。
+请注意,Privacy Mode off 意味着 bot 在技术上可以收到所有消息,本 bot 已经设计为不处理及不记录任何无关消息,如您疑虑安全性,请自行搭建或到末尾查看更多解决方案。
## Usage
+
```sh
docker create \
--name [container name] \
@@ -19,6 +20,7 @@ docker create \
-e TOKEN=[bot token] \
rongronggg9/slashbot
```
+
```sh
docker start [container name]
```
@@ -26,12 +28,14 @@ docker start [container name]
![](resources/example.jpg)
## 薛定谔的 Telegram Bot API
+
若您的群组存在大于一个 bot (含本 bot),本 bot 可能时常无法正常工作。
症状:
[@RongSlashBot](https://t.me/RongSlashBot) 突然接收不到任何指令消息,因而也无法回复。
触发条件 (需全部满足):
+
1. 群组内存在大于一个 bot
2. 该 bot 未被设置为管理员
3. 该 bot Privacy Mode on
@@ -39,7 +43,8 @@ docker start [container name]
总体上,管理员或 Privacy Mode off 的 bot 几乎一定会收到消息,其余 bot 可能收到也可能收不到消息。
-**规避方法 (满足任一即可):**
+**规避方法 (满足任一即可):**
+
1. **仅保留本 bot**
2. **将本 bot 添加为管理员(给予任一权限均可。需注意该操作意味着对于本群,该 bot Privacy Mode off)**
3. **换用 Privacy Mode off 的 [@RongSlashRBot](https://t.me/RongSlashRBot) (如您疑虑安全性,请自行搭建)**
diff --git a/SlashBot.py b/SlashBot.py
index 4ef9b8f..792ae01 100644
--- a/SlashBot.py
+++ b/SlashBot.py
@@ -1,106 +1,87 @@
import os
import re
-from typing import List, Dict, Union, Tuple
-
import requests
+import telegram
+from typing import Tuple, Optional, Callable
from telegram.ext import Updater, MessageHandler, filters
-TELEGRAM = 777000
-GROUP = 1087968824
Filters = filters.Filters
-parser = re.compile(r'^([\\/]_?)((?:[^ \\]|\\.)+)[ ]*(.*)$')
-escaping = ('\\ ', '\\ ')
-markdownEscape = lambda s: s.replace("_", "\\_").replace("*", "\\*").replace("[", "\\[").replace("`", "\\`")
+parser = re.compile(r'^([\\/]_?)((?:[^ \t\\]|\\.)+)[ \t]*(.*)$')
+ESCAPING = ('\\ ', '\\ ', '\\\t')
+htmlEscape = lambda s: s.replace("<", "<").replace(">", ">").replace("&", "&")
+mentionParser = re.compile(r'@([a-zA-Z]\w{4,})$')
+delUsername: Optional[Callable] = None # placeholder
# Docker env
Token = os.environ.get('TOKEN')
if not Token:
raise Exception('no token')
-if os.environ.get('PROXY'):
- telegram_proxy = os.environ['PROXY']
- requests_proxies = {'all': os.environ['PROXY']}
-else:
- telegram_proxy = ''
- requests_proxies = None
+telegram_proxy = os.environ.get('PROXY', '')
+requests_proxies = {'all': telegram_proxy} if telegram_proxy else None
-# Find someone's full name by their username
-def find_name_by_username(username: str) -> str:
- r = requests.get(f'https://t.me/{username}', proxies=requests_proxies)
- return re.search('(?<=).*(?=)', r.text, re.IGNORECASE).group(0)
+ if page_title == self.name: # user does not exist
+ self.name = None
+
+ def mention(self, mention_self: bool = False) -> str:
+ if not self.name:
+ return f'@{self.username}'
+
+ 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}')
+ return f'{self.name if not mention_self else "自己"}'
+
+ 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)
+ )
+ )
-def get_user(msg):
- if msg['from']['id'] == TELEGRAM:
- return {'first_name': msg['sender_chat']['title'], 'id': msg['sender_chat']['id'],
- 'username': msg['sender_chat'].get('username')}
- elif msg['from']['id'] == GROUP:
- return {'first_name': msg['chat']['title'], 'id': msg['chat']['id'], 'username': msg['chat'].get('username')}
- else:
- return msg['from']
+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)
-def get_users(msg: Dict) -> Tuple[Dict, Dict, bool, bool]:
+def get_users(msg: telegram.Message) -> Tuple[User, User]:
msg_from = msg
- if 'reply_to_message' in msg.keys():
- msg_rpl = msg['reply_to_message']
- else:
- msg_rpl = msg_from.copy()
+ msg_rpl = msg.reply_to_message or msg_from
from_user, rpl_user = get_user(msg_from), get_user(msg_rpl)
- reply_self = rpl_user == from_user
- mentioned = False
-
- # Not replying to anything
- if reply_self:
-
- # Detect if the message contains a mention. If it has, use the mentioned user.
- entities: List[Dict[str, Union[str, int]]] = msg['entities']
- mentions = [e for e in entities if e['type'] == 'mention']
- if mentions:
- mentioned = True
-
- # Find username
- offset = mentions[0]['offset']
- length = mentions[0]['length']
- text = msg['text']
- username = text[offset: offset + length].replace("@", "")
- rpl_user = {'first_name': find_name_by_username(username), 'username': username}
-
- # Remove mention from message text
- msg['text'] = text[:offset] + text[offset + length:]
-
- else:
- rpl_user = {'first_name': '自己', 'id': rpl_user['id']}
-
- return from_user, rpl_user, reply_self, mentioned
+ return from_user, rpl_user
-# Create mention string from user
-def mention(user: Dict[str, str]) -> str:
- # Combine name
- last = user.get('last_name', '')
- first = user['first_name']
- name = first + (f' {last}' if last else '')
-
- # Create user reference link
- username = user.get('username', '')
- uid = user.get('id', -1)
- link = f'tg://resolve?domain={username}' if (username and uid < 0) else f'tg://user?id={uid}'
-
- return f"[{name}]({link})"
-
-
-def parse_command(command):
- parsed = list(parser.search(command).groups())
+def parse_command(match: re.Match):
+ parsed = match.groups()
predicate = parsed[1]
- for escape in escaping:
+ for escape in ESCAPING:
+ predicate = delUsername('', predicate)
predicate = predicate.replace(escape, escape[1:])
- result = {'predicate': markdownEscape(predicate), 'complement': markdownEscape(parsed[2]), 'swap': parsed[0] != '/'}
+ result = {'predicate': htmlEscape(predicate), 'complement': htmlEscape(parsed[2]), 'swap': parsed[0] != '/'}
return result
-def get_text(mention_from, mention_rpl, command):
+def get_text(user_from: User, user_rpl: User, command: dict):
+ mention_from = user_from.mention()
+ mention_rpl = user_rpl.mention(mention_self=user_from == user_rpl)
if command['predicate'] == 'me':
return f"{mention_from}{bool(command['complement']) * ' '}{command['complement']}!"
elif command['predicate'] == 'you':
@@ -111,24 +92,31 @@ def get_text(mention_from, mention_rpl, command):
return f"{mention_from} {command['predicate']} 了 {mention_rpl}!"
-def reply(update, context):
+def reply(update: telegram.Update, context: telegram.ext.CallbackContext):
print(update.to_dict())
- msg = update.to_dict()['message']
- from_user, rpl_user, reply_self, mentioned = get_users(msg)
+ msg = update.effective_message
+ from_user, rpl_user = get_users(msg)
+ command = parse_command(context.match)
- command = parse_command(del_username.sub('', msg['text']))
- if command['swap'] and (not reply_self or mentioned):
+ 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()]
+
+ if command['swap'] and (not from_user == rpl_user):
(from_user, rpl_user) = (rpl_user, from_user)
- text = get_text(mention(from_user), mention(rpl_user), command)
+ text = get_text(from_user, rpl_user, command)
print(text, end='\n\n')
- update.effective_message.reply_text(text, parse_mode='Markdown')
+ update.effective_message.reply_text(text, parse_mode='HTML')
if __name__ == '__main__':
updater = Updater(token=Token, use_context=True, request_kwargs={'proxy_url': telegram_proxy})
- del_username = re.compile('@' + updater.bot.username, re.I)
+ delUsername = re.compile('@' + updater.bot.username, re.I).sub
dp = updater.dispatcher
dp.add_handler(MessageHandler(Filters.regex(parser), reply))