diff --git a/README.md b/README.md index c1a555c..f21edff 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ rssbotLib=rssbot.dll - [rssbotLib](#rssbotlib) - [databaseLocation](#databaselocation) - [retryTTL](#retryttl) +- [botOwnerList](#botownerlist) ### token 必填参数。Telegram Bot API Token。向[@BotFather](https://t.me/BotFather)请求新建Bot,即可得到。 ### maxCount @@ -47,3 +48,5 @@ rssbotLib=rssbot.dll 可选参数。数据库位置。默认值为`data.db`。 ### retryTTL 可选参数。RSS更新发生错误后,再次更新的间隔时间。默认值为`30`。单位为分。 +### botOwnerList +可选参数。具有特殊权限的用户ID,这些用户可以使用部分特殊功能。可以使用`,`分隔两个不同的ID。 diff --git a/RSSEntry.py b/RSSEntry.py index a064ddb..1c67da0 100644 --- a/RSSEntry.py +++ b/RSSEntry.py @@ -136,5 +136,8 @@ class RSSEntry: self.lasterrortime = None if data is not None and data[5] is not None: self.lasterrortime = data[5] + self.forceupdate = None + if data is not None and data[6] is not None: + self.forceupdate = data[6] self.chatList = [] self.hashList = HashEntries(maxCount) diff --git a/botOwner.py b/botOwner.py new file mode 100644 index 0000000..4ca29cc --- /dev/null +++ b/botOwner.py @@ -0,0 +1,38 @@ +# (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 . +from re import search +from typing import List + + +class BotOwnerList: + def __init__(self, m, s: str = None): + from rssbot import main + self._main: main = m + self.__list = [] + if s is not None: + l = s.split(',') + for i in l: + if search(r'^[\+-]?[0-9]+$', i) is not None: + self.__list.append(int(i)) + + def getOwnerList(self) -> List[int]: + r = [] + for i in self.__list: + r.append(i) + return r + + def isOwner(self, chatId: int) -> bool: + return chatId in self.__list diff --git a/database.py b/database.py index 57407b2..e099282 100644 --- a/database.py +++ b/database.py @@ -50,6 +50,11 @@ class database: if v == [1, 0, 0, 0]: self._db.execute('ALTER TABLE RSSList ADD lasterrortime INT;') self._db.commit() + if v < [1, 0, 0, 2]: + self._db.execute( + 'ALTER TABLE RSSList ADD forceupdate BOOLEAN;') + self._db.execute('UPDATE RSSList SET forceupdate=false;') + self._db.commit() self.__write_version() return True @@ -70,6 +75,7 @@ interval INT, lastupdatetime INT, id TEXT, lasterrortime INT, +forceupdate BOOLEAN, PRIMARY KEY (id) );''') if 'chatList' not in self._exist_tables: @@ -95,7 +101,7 @@ PRIMARY KEY (hash) self._db.commit() def __init__(self, m, loc: str): - self._version = [1, 0, 0, 1] + self._version = [1, 0, 0, 2] self._value_lock = Lock() self._db = sqlite3.connect(loc, check_same_thread=False) ok = self.__check_database() @@ -139,7 +145,7 @@ PRIMARY KEY (hash) f"UPDATE RSSList SET title='{dealtext(title)}', interval={ttl if ttl is not None else 'null'} WHERE id='{hashd}'") else: self._db.execute( - f"INSERT INTO RSSList VALUES ('{dealtext(title)}', '{dealtext(url)}', {ttl if ttl is not None else 'null'}, {int(time())}, '{hashd}', null)") + f"INSERT INTO RSSList VALUES ('{dealtext(title)}', '{dealtext(url)}', {ttl if ttl is not None else 'null'}, {int(time())}, '{hashd}', null, false)") cur = self._db.execute( f'SELECT * FROM chatList WHERE id="{hashd}" AND chatId={chatId}') has_data2 = False @@ -193,11 +199,11 @@ PRIMARY KEY (hash) def getRSSListByChatId(self, chatId: int) -> List[RSSEntry]: with self._value_lock: cur = self._db.execute( - f"SELECT RSSList.title, RSSList.url, RSSList.interval, RSSList.lastupdatetime, RSSList.id, RSSList.lasterrortime, chatList.config FROM RSSList, chatList WHERE chatList.chatId = {chatId} AND RSSList.id = chatList.id ORDER BY title") + f"SELECT RSSList.title, RSSList.url, RSSList.interval, RSSList.lastupdatetime, RSSList.id, RSSList.lasterrortime, RSSList.forceupdate, chatList.config FROM RSSList, chatList WHERE chatList.chatId = {chatId} AND RSSList.id = chatList.id ORDER BY title") RSSEntries = [] for i in cur: rssEntry = RSSEntry(i, self._main._setting._maxCount) - rssEntry.chatList.append(ChatEntry((chatId, i[4], i[6]))) + rssEntry.chatList.append(ChatEntry((chatId, i[4], i[7]))) RSSEntries.append(rssEntry) return RSSEntries @@ -222,6 +228,25 @@ PRIMARY KEY (hash) except: return False + def setRSSForceUpdate(self, url: str, forceupdate: bool) -> bool: + 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: # pylint: disable=unused-variable + has_data = True + break + if not has_data: + return False + self._db.execute( + f"UPDATE RSSList SET forceupdate={'true' if forceupdate else 'false'} WHERE id='{hashd}'") + self._db.commit() + return True + except: + return False + def setUserStatus(self, userId: int, status: userStatus = userStatus.normalStatus, hashd: str = '') -> bool: with self._value_lock: try: @@ -296,6 +321,7 @@ PRIMARY KEY (hash) self._db.execute( f"INSERT INTO hashList VALUES ('{v.id}', '{v.hash}', {v.time})") self._db.commit() + return True except: return False @@ -314,5 +340,6 @@ PRIMARY KEY (hash) self._db.execute( f"UPDATE RSSList SET lasterrortime={lasterrortime} WHERE id='{hashd}'") self._db.commit() + return True except: return False diff --git a/readset.py b/readset.py index 6a95ea9..fe6544f 100644 --- a/readset.py +++ b/readset.py @@ -15,10 +15,13 @@ # along with this program. If not, see . from typing import List from getopt import getopt +from botOwner import BotOwnerList class settings: - def __init__(self, fn: str = None): + def __init__(self, m, fn: str = None): + from rssbot import main + self._main: main = m if fn is not None: self.parse(fn) @@ -46,7 +49,10 @@ class settings: d['sendFileURLScheme'])) if 'sendFileURLScheme' in d and d['sendFileURLScheme'].isnumeric() else False self._rssbotLib = d['rssbotLib'] if 'rssbotLib' in d and d['rssbotLib'] != '' else None self._databaseLocation = d['databaseLocation'] if 'databaseLocation' in d and d['databaseLocation'] != '' else 'data.db' - self._retryTTL = int(d['retryTTL']) if 'retryTTL' in d and d['retryTTL'].isnumeric() and int(d['retryTTL']) > 0 else 30 + self._retryTTL = int(d['retryTTL']) if 'retryTTL' in d and d['retryTTL'].isnumeric( + ) and int(d['retryTTL']) > 0 else 30 + self._botOwnerList = BotOwnerList( + self._main, d['botOwnerList']) if 'botOwnerList' in d and d['botOwnerList'] != '' else BotOwnerList(self._main) class commandline: diff --git a/rssbot.py b/rssbot.py index c145759..539cf8e 100644 --- a/rssbot.py +++ b/rssbot.py @@ -420,7 +420,7 @@ class main: self._upi = i['update_id'] + 1 def start(self): - self._setting = settings('settings.txt') + self._setting = settings(self, 'settings.txt') if self._setting._token is None: print('没有机器人token') return -1 @@ -1060,7 +1060,7 @@ class callbackQueryHandle(Thread): di['text'] = getTextContentForRSSInList(rssList[ind]) di['parse_mode'] = 'HTML' di['reply_markup'] = getInlineKeyBoardForRSSInList( - chatId, rssList[ind], ind) + chatId, rssList[ind], ind, self._main._setting._botOwnerList) self._main._request("editMessageText", "post", json=di) self.answer() return @@ -1156,6 +1156,15 @@ class callbackQueryHandle(Thread): chatId, rssList[ind], ind) self._main._request("editMessageText", "post", json=di) return + elif self._inlineKeyBoardForRSSListCommand == InlineKeyBoardForRSSList.ForceUpdate: + rssList = self._main._db.getRSSListByChatId(chatId) + ind = int(self._inputList[3]) + ind = max(min(ind, len(rssList)), 0) + if self._main._db.setRSSForceUpdate(rssList[ind].url, True): + self.answer('已发送强制更新请求。') + else: + self.answer('发送强制更新请求失败。') + return else: self.answer('未知的按钮。') return diff --git a/rsschecker.py b/rsschecker.py index f1a0f0c..f4f0326 100644 --- a/rsschecker.py +++ b/rsschecker.py @@ -69,6 +69,8 @@ class RSSCheckerThread(Thread): except: print(format_exc()) self._main._db.updateRSSWithError(rss.url, int(time())) + if rss.forceupdate: + self._main._db.setRSSForceUpdate(rss.url, False) if self._main._commandLine._rebuildHashlist and self._main._commandLine._exitAfterRebuild: _exit(0) self._main._commandLine._rebuildHashlist = False @@ -80,6 +82,8 @@ class RSSCheckerThread(Thread): self._main: main = m def __needUpdate(self, rss: RSSEntry): + if rss.forceupdate: + return True if rss.lasterrortime is not None and rss.lasterrortime >= rss.lastupdatetime: return True if int(time()) > rss.lasterrortime + self._main._setting._retryTTL * 60 else False if rss.lastupdatetime is None: diff --git a/rsslist.py b/rsslist.py index b8da9bc..9af54c7 100644 --- a/rsslist.py +++ b/rsslist.py @@ -18,6 +18,7 @@ from typing import List from enum import Enum, unique from math import ceil, floor from textc import textc, timeToStr +from botOwner import BotOwnerList @unique @@ -39,6 +40,7 @@ class InlineKeyBoardForRSSList(Enum): ShowContentTitle = 14 ShowContent = 15 SendMedia = 16 + ForceUpdate = 17 def getTextContentForRSSInList(rssEntry: RSSEntry) -> str: @@ -113,13 +115,18 @@ def getInlineKeyBoardForRSSList(chatId: int, RSSEntries: List[RSSEntry], page: i return {'inline_keyboard': d} -def getInlineKeyBoardForRSSInList(chatId: int, rssEntry: RSSEntry, index: int) -> dict: +def getInlineKeyBoardForRSSInList(chatId: int, rssEntry: RSSEntry, index: int, botOwnerList: BotOwnerList = None) -> dict: d = [] i = -1 d.append([]) i = i + 1 d[i].append( {'text': '取消订阅', 'callback_data': f'1,{chatId},{InlineKeyBoardForRSSList.Unsubscribe.value},{index}'}) + if botOwnerList is not None and botOwnerList.isOwner(chatId): + d.append([]) + i = i + 1 + d[i].append( + {'text': '强制更新', 'callback_data': f'1,{chatId},{InlineKeyBoardForRSSList.ForceUpdate.value},{index}'}) d.append([]) i = i + 1 d[i].append(