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(