This commit is contained in:
2021-01-06 23:30:59 +08:00
parent bb0065c762
commit bef75562ae
5 changed files with 388 additions and 40 deletions

View File

@@ -14,6 +14,18 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from json import loads
from config import RSSConfig
class ChatEntry:
def __init__(self, data=None):
self.chatId = data[0] if data is not None and data[0] is not None else None
self.id = data[1] if data is not None and data[1] is not None else None
try:
self.config = RSSConfig(
loads(data[2])) if data is not None and data[2] is not None else RSSConfig()
except:
self.config = RSSConfig()
class RSSEntry:
@@ -30,12 +42,7 @@ class RSSEntry:
self.lastupdatetime = None
if data is not None and data[3] is not None:
self.interval = data[3]
self.config = None
if data is not None and data[4] is not None:
try:
self.config = loads(data[4])
except:
pass
self.id = None
if data is not None and data[5] is not None:
self.id = data[5]
if data is not None and data[4] is not None:
self.id = data[4]
self.chatList = []

30
config.py Normal file
View File

@@ -0,0 +1,30 @@
# (C) 2021 lifegpc
# This file is part of rssbot.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from json import dumps
class RSSConfig:
def __init__(self, d: dict = None):
self.disable_web_page_preview = False
self.show_RSS_title = True
self.show_Content_title = True
if d is not None:
for k in d.keys():
if hasattr(self, k):
setattr(self, k, d[k])
def toJson(self):
return dumps({'disable_web_page_preview': self.disable_web_page_preview, 'show_RSS_title': self.show_RSS_title, 'show_Content_title': self.show_Content_title}, ensure_ascii=False)

View File

@@ -14,17 +14,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sqlite3
from RSSEntry import RSSEntry
from config import RSSConfig
from RSSEntry import RSSEntry, ChatEntry
from typing import List
from enum import Enum, unique
from threading import Lock
from hashl import sha256WithBase64
class RSSConfig:
def __init__(self):
self.disable_web_page_preview = False
self.show_RSS_title = True
self.show_Content_title = True
def dealtext(s: str):
return s.replace("'", "''")
@unique
@@ -60,14 +59,14 @@ title TEXT,
url TEXT,
interval INT,
lastupdatetime INT,
config TEXT,
id TEXT,
PRIMARY KEY (id)
);''')
if 'userList' not in self._exist_tables:
self._db.execute('''CREATE TABLE userList (
userId INT,
id TEXT
if 'chatList' not in self._exist_tables:
self._db.execute('''CREATE TABLE chatList (
chatId INT,
id TEXT,
config TEXT
)''')
if 'userStatus' not in self._exist_tables:
self._db.execute('''CREATE TABLE userStatus (
@@ -75,16 +74,12 @@ userId INT,
status INT,
hashd TEXT,
PRIMARY KEY (userId)
)''')
if 'channelList' not in self._exist_tables:
self._db.execute('''CREATE TABLE channelList (
channelId INT,
id TEXT
)''')
if 'hashList' not in self._exist_tables:
self._db.execute('''CREATE TABLE hashList (
id TEXT,
hash TEXT,
time INT,
PRIMARY KEY (hash)
)''')
self._db.commit()
@@ -97,17 +92,63 @@ PRIMARY KEY (hash)
if not ok:
self.__create_table()
def __removeRSSEntry(self, id: str) -> bool:
try:
self._db.execute(f'DELETE FROM RSSList WHERE id="{id}"')
self._db.execute(f'DELETE FROM hashList WHERE id="{id}"')
self._db.commit()
return True
except:
return False
def __write_version(self):
self._db.execute(
f'INSERT INTO config VALUES ({self._version[0]}, {self._version[1]}, {self._version[2]}, {self._version[3]});')
self._db.commit()
def addRSSList(self, title: str, url: str, chatId: int, config: RSSConfig, ttl: int = None):
with self._value_lock:
try:
hashd = sha256WithBase64(url)
cur = self._db.execute(
f'SELECT * FROM RSSList WHERE id="{hashd}"')
has_data = False
for i in cur:
has_data = True
if has_data:
self._db.execute(f'DELETE FROM RSSList WHERE id="{hashd}"')
self._db.execute(
f"INSERT INTO RSSList VALUES ('{dealtext(title)}', '{dealtext(url)}', {ttl if ttl is not None else 'null'}, null, '{hashd}')")
cur = self._db.execute(
f'SELECT * FROM chatList WHERE id="{hashd}" AND chatId={chatId}')
has_data = False
for i in cur:
has_data = True
if has_data:
self._db.execute(
f'DELETE FROM chatList WHERE id="{hashd}" AND chatId={chatId}')
self._db.execute(
f"INSERT INTO chatList VALUES ({chatId}, '{hashd}', '{dealtext(config.toJson())}')")
self._db.commit()
return True
except:
return False
def getAllRSSList(self) -> List[RSSEntry]:
with self._value_lock:
cur = self._db.execute(f'SELECT * FROM RSSList;')
r = []
for i in cur:
r.append(RSSEntry(i))
temp = RSSEntry(i)
cur2 = self._db.execute(
f'SELECT * FROM chatList WHERE id="{temp.id}"')
for i2 in cur2:
temp2 = ChatEntry(i2)
temp.chatList.append(temp2)
if len(temp.chatList) == 0:
self.__removeRSSEntry(temp.id)
else:
r.append(temp)
return r
def getUserStatus(self, userId: int) -> (userStatus, str):

294
rssbot.py
View File

@@ -27,6 +27,7 @@ from enum import Enum
from rsstempdict import rssMetaInfo, rssMetaList
from random import randrange
from textc import textc
from re import search, I
def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str:
@@ -45,6 +46,10 @@ def getMediaInfo(m: dict, config: RSSConfig = RSSConfig()) -> str:
s = f"""{s}\n群/频道ID:{m['chatId']}"""
elif 'userId' in m and m['userId'] is not None:
s = f"""{s}\n<a href="tg://user?id={m['userId']}">订阅的账号</a>"""
s = f"{s}\n设置:"
s = f"{s}\n禁用预览:{config.disable_web_page_preview}"
s = f"{s}\n显示RSS标题:{config.show_RSS_title}"
s = f"{s}\n显示内容标题:{config.show_Content_title}"
return s
@@ -53,6 +58,11 @@ class InlineKeyBoardCallBack(Enum):
SendPriview = 1
ModifyChatId = 2
BackUserId = 3
SettingsPage = 4
BackToNormalPage = 5
DisableWebPagePreview = 6
ShowRSSTitle = 7
ShowContentTitle = 8
def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str:
@@ -69,8 +79,6 @@ def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str:
d[i].append(
{'text': '修改群组/频道ID', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ModifyChatId.value}'})
if 'userId' in m and m['userId'] is not None:
d.append([])
i = i + 1
d[i].append(
{'text': '发送至私聊', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.BackUserId.value}'})
elif 'userId' in m and m['userId'] is not None:
@@ -78,6 +86,30 @@ def getInlineKeyBoardWhenRSS(hashd: str, m: dict) -> str:
i = i + 1
d[i].append(
{'text': '发送至群组/频道', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ModifyChatId.value}'})
d.append([])
i = i + 1
d[i].append(
{'text': '设置', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.SettingsPage.value}'})
return {'inline_keyboard': d}
def getInlineKeyBoardWhenRSS2(hashd: str, config: RSSConfig) -> str:
d = []
i = 0
temp = '启用预览' if config.disable_web_page_preview else '禁用预览'
d.append([])
d[i].append(
{'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.DisableWebPagePreview.value}'})
temp = '隐藏RSS标题' if config.show_RSS_title else '显示RSS标题'
d[i].append(
{'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ShowRSSTitle.value}'})
d.append([])
i = i + 1
temp = '隐藏内容标题' if config.show_Content_title else '显示内容标题'
d[i].append(
{'text': temp, 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.ShowContentTitle.value}'})
d[i].append(
{'text': '返回', 'callback_data': f'0,{hashd},{InlineKeyBoardCallBack.BackToNormalPage.value}'})
return {'inline_keyboard': d}
@@ -108,11 +140,14 @@ class main:
if config.show_RSS_title:
text.addtotext(f"<b>{escape(meta['title'])}</b>")
if config.show_Content_title and 'title' in content and content['title'] is not None and content['title'] != '':
if 'link' in content:
if 'link' in content and content['link'] is not None and content['link'] != '':
text.addtotext(
f"""<b><a href="{content['link']}">{escape(content['title'])}</a></b>""")
else:
text.addtotext(f"<b>{escape(content['title'])}</b>")
elif 'link' in content and content['link'] is not None and content['link'] != '':
text.addtotext(
f"""<a href="{content['link']}">{escape(content['link'])}</a>""")
if 'description' in content and content['description'] is not None and content['description'] != '':
text.addtotext(content['description'])
@@ -193,9 +228,11 @@ class main:
return -1
self._r = Session()
self._me = self._request('getMe')
self._rssMetaList = rssMetaList()
print(self._me)
if self._me is None:
if self._me is None or 'ok' not in self._me or not self._me['ok']:
print('无法读取机器人信息')
self._me = self._me['result']
self._upi = None
self._updateThread = updateThread(self)
self._updateThread.start()
@@ -244,10 +281,11 @@ class messageHandle(Thread):
def _getCommandlinePara(self) -> List[str]:
s = self._data['text']
for i in self._data['entities']:
if i['type'] == 'bot_command':
s = s[:i['offset']] + s[i['offset']+i['length']:]
break
if 'entities' in self._data:
for i in self._data['entities']:
if i['type'] == 'bot_command':
s = s[:i['offset']] + s[i['offset']+i['length']:]
break
l = s.split(' ')
r = []
t = ''
@@ -277,6 +315,9 @@ class messageHandle(Thread):
def run(self):
print(self._data)
for key in ["new_chat_members", "left_chat_member", "new_chat_title", "new_chat_photo", "delete_chat_photo", "pinned_message"]:
if key in self._data:
return
self._messageId = self._data['message_id']
self._chatId = self.__getChatId()
if self._chatId is None:
@@ -286,10 +327,138 @@ class messageHandle(Thread):
if 'text' in self._data:
if 'entities' in self._data:
self._botCommand = self.__getBotCommand()
self._fromUserId = self.__getFromUserId()
if self._fromUserId is not None:
di = {'chat_id': self._chatId}
if self.__getChatType() in ['supergroup', 'group'] and self._fromUserId is not None:
di['reply_to_message_id'] = self._messageId
self._userStatus, self._hashd = self._main._db.getUserStatus(
self._fromUserId)
if self._userStatus == userStatus.normalStatus:
pass
elif self._botCommand is not None and self._botCommand == '/cancle':
if self._userStatus == userStatus.needInputChatId:
di['text'] = '已取消输入群/频道ID。'
self._main._db.setUserStatus(
self._fromUserId, userStatus.normalStatus)
self._main._request('sendMessage', 'post', json=di)
return
elif self._userStatus in [userStatus.needInputChatId]:
metainfo = self._main._rssMetaList.getRSSMeta(self._hashd)
if metainfo is None:
self._main._db.setUserStatus(
self._fromUserId, userStatus.normalStatus)
di['text'] = '已过期。'
self._main._request('sendMessage', 'post', json=di)
return
elif self._userStatus == userStatus.needInputChatId:
para = self._getCommandlinePara()
chatId = None
for i in para:
if search(r'^[\+-]?[0-9]+$', i) is not None:
chatId = int(i)
break
if chatId is None:
di['text'] = '找不到ID。'
self._main._request('sendMessage', 'post', json=di)
return
di['text'] = '正在获取群/频道信息……'
re = self._main._request('sendMessage', 'post', json=di)
if re is not None and 'ok' in re and re['ok']:
re = re['result']
di = {'chat_id': self._chatId,
'message_id': re['message_id']}
re2 = self._main._request(
'getChat', 'post', {'chat_id': chatId})
if re2 is not None and 'ok' in re2 and re2['ok']:
re2 = re2['result']
if re2['type'] == "private":
di['text'] = '该ID是私聊。'
self._main._request(
'editMessageText', 'post', json=di)
return
di['text'] = '正在获取群/频道管理员列表……'
self._main._request(
'editMessageText', 'post', json=di)
re3 = self._main._request(
'getChatAdministrators', 'post', {'chat_id': chatId})
if re3 is not None and 'ok' in re3 and re3['ok']:
re3 = re3['result']
chatM = None
for chatMember in re3:
if chatMember['user']['id'] != self._fromUserId:
continue
if chatMember['status'] not in ['creator', 'administrator']:
continue
if re2['type'] == 'channel' and ('can_post_messages' not in chatMember or not chatMember['can_post_messages']):
continue
if re2['type'] == 'channel' and ('can_edit_messages' not in chatMember or not chatMember['can_edit_messages']):
continue
chatM = chatMember
if chatM is None:
di['text'] = '你没有权限操作。'
self._main._request(
'editMessageText', 'post', json=di)
return
di['text'] = '正在确认机器人的权限……'
self._main._request(
'editMessageText', 'post', json=di)
re4 = self._main._request("getChatMember", "post", {
"chat_id": chatId, "user_id": self._main._me['id']})
if re4 is not None and 'ok' in re4 and re4['ok']:
re4 = re4['result']
if re2['type'] == 'channel' and (re4['status'] not in ['creator', 'administrator'] or not re4['can_post_messages'] or not re4['can_edit_messages']):
di['text'] = '机器人在频道内缺少必要的权限'
self._main._request(
'editMessageText', 'post', json=di)
return
if re2['type'] in ["group", "supergroup"]:
if re4['status'] in ['creator', 'administrator']:
pass
elif 'permissions' in re2 and re2['permissions'] is not None and (not re2['permissions']['can_send_messages'] or not re2['permissions']['can_send_media_messages'] or not re2['permissions']['can_send_other_messages'] or not re2['permissions']['can_add_web_page_previews']):
di['text'] = '机器人在群组内缺少必要的权限'
self._main._request(
'editMessageText', 'post', json=di)
return
elif re4['status'] in ['left', 'kicked']:
di['text'] = '机器人不在群组内' if re4['status'] == 'lefy' else '机器人已被踢'
self._main._request(
'editMessageText', 'post', json=di)
return
elif re4['status'] == 'restricted' and (not re4['can_send_messages'] or not re4['can_send_media_messages'] or not re4['can_send_other_messages'] or not re4['can_add_web_page_previews']):
di['text'] = '机器人在群组内缺少必要的权限'
self._main._request(
'editMessageText', 'post', json=di)
return
else:
di['text'] = '获取机器人权限失败!'
self._main._request(
'editMessageText', 'post', json=di)
return
else:
di['text'] = '获取群/频道信息失败!'
self._main._request(
'editMessageText', 'post', json=di)
return
metainfo.meta['chatId'] = chatId
metainfo.flushTime()
di['text'] = '修改完成!'
self._main._request('editMessageText', 'post', json=di)
di = {'chat_id': metainfo.chatId,
'message_id': metainfo.messageId}
di['text'] = getMediaInfo(
metainfo.meta, metainfo.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS(
self._hashd, metainfo.meta)
self._main._request('editMessageText', 'post', json=di)
self._main._db.setUserStatus(
self._fromUserId, userStatus.normalStatus)
return
if self._botCommand is None or self._botCommand not in ['/help', '/rss']:
self._botCommand = '/help'
di = {'chat_id': self._chatId}
self._fromUserId = self.__getFromUserId()
if self.__getChatType() in ['supergroup', 'group'] and self._fromUserId is not None:
di['reply_to_message_id'] = self._messageId
if self._botCommand == '/help':
@@ -336,7 +505,7 @@ class messageHandle(Thread):
di['reply_markup'] = getInlineKeyBoardWhenRSS(
self._hash, media)
self._main._rssMetaList.addRSSMeta(rssMetaInfo(
re['message_id'], media, p.itemList, self._hash))
re['message_id'], chatId, media, p.itemList, self._hash))
self._main._request('editMessageText', 'post', json=di)
@@ -346,7 +515,7 @@ class callbackQueryHandle(Thread):
self._main = main
self._data = data
def answer(self, text: str):
def answer(self, text: str = ''):
di = {}
di['callback_query_id'] = self._callbackQueryId
di['text'] = text
@@ -376,12 +545,31 @@ class callbackQueryHandle(Thread):
if self._rssMeta is None:
self.answer('找不到数据。可能已经超时。')
return
self._rssMeta.flushTime()
self._userId = None
if 'userId' in self._rssMeta.meta and self._rssMeta.meta['userId'] is not None:
self._userId = self._rssMeta.meta['userId']
if self._userId is not None and self._data['from']['id'] != self._userId:
self.answer('你没有权限操作。')
if self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SendPriview:
if self._inlineKeyBoardCommand == InlineKeyBoardCallBack.Subscribe:
title = self._rssMeta.meta['title']
url = self._rssMeta.meta['url']
chatId = None
if 'chatId' in self._rssMeta.meta and self._rssMeta.meta['chatId'] is not None:
chatId = self._rssMeta.meta['chatId']
elif self._userId is not None:
chatId = self._userId
if chatId is None:
self.answer('缺少发送的位置')
return
config = self._rssMeta.config
ttl = self._rssMeta.meta['ttl'] if 'ttl' in self._rssMeta.meta else None
suc = self._main._db.addRSSList(title, url, chatId, config, ttl)
if suc:
self.answer('订阅成功!')
else:
self.answer('订阅失败!')
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SendPriview:
chatId = None
if 'chatId' in self._rssMeta.meta and self._rssMeta.meta['chatId'] is not None:
chatId = self._rssMeta.meta['chatId']
@@ -402,14 +590,92 @@ class callbackQueryHandle(Thread):
self.answer(f'{ran}条发送失败!')
return
elif self._userId is not None and self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ModifyChatId:
self._main._db.setUserStatus(self._userId, userStatus.needInputChatId, self._hashd)
self._main._db.setUserStatus(
self._userId, userStatus.needInputChatId, self._hashd)
di = {}
if 'message' in self._data and self._data['message'] is not None:
di['chat_id'] = self._data['message']['chat']['id']
else:
di['chat_id'] = self._data['from']['id']
di["text"] = "请输入群/频道的ID"
di["text"] = "请输入群/频道的ID(使用 /cancle 可以取消):"
self._main._request("sendMessage", "post", json=di)
self.answer()
return
elif self._userId is not None and self._inlineKeyBoardCommand == InlineKeyBoardCallBack.BackUserId:
self._rssMeta.meta['chatId'] = None
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS(
self._hashd, self._rssMeta.meta)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.SettingsPage:
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS2(
self._hashd, self._rssMeta.config)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.BackToNormalPage:
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS(
self._hashd, self._rssMeta.meta)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.DisableWebPagePreview:
self._rssMeta.config.disable_web_page_preview = not self._rssMeta.config.disable_web_page_preview
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS2(
self._hashd, self._rssMeta.config)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ShowRSSTitle:
self._rssMeta.config.show_RSS_title = not self._rssMeta.config.show_RSS_title
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS2(
self._hashd, self._rssMeta.config)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
elif self._inlineKeyBoardCommand == InlineKeyBoardCallBack.ShowContentTitle:
self._rssMeta.config.show_Content_title = not self._rssMeta.config.show_Content_title
di = {'chat_id': self._rssMeta.chatId,
'message_id': self._rssMeta.messageId}
di['text'] = getMediaInfo(
self._rssMeta.meta, self._rssMeta.config)
di['parse_mode'] = 'HTML'
di['disable_web_page_preview'] = True
di['reply_markup'] = getInlineKeyBoardWhenRSS2(
self._hashd, self._rssMeta.config)
self._main._request("editMessageText", "post", json=di)
self.answer()
return
else:
self.answer('未知的按钮。')

View File

@@ -18,8 +18,9 @@ from database import RSSConfig
class rssMetaInfo:
def __init__(self, messageId: int, meta: dict, itemList: list, hashd: str, config: RSSConfig = None):
def __init__(self, messageId: int, chatId: int, meta: dict, itemList: list, hashd: str, config: RSSConfig = None):
self.messageId = messageId
self.chatId = chatId
self.meta = meta
self.itemList = itemList
self.hashd = hashd
@@ -29,6 +30,9 @@ class rssMetaInfo:
def isTimeOut(self):
return True if time() - self.__time > 300 else False
def flushTime(self):
self.__time = time()
class rssMetaList:
def __init__(self):