From 4cb10597c59a4c282c9c20fc544ccccaab616e2b Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 9 May 2021 17:19:10 +0800 Subject: [PATCH] add mirai --- README.md | 5 +- mirai.py | 186 +++++++++++++++++++++++++++++++++++++++++++++++ miraiDatabase.py | 74 ++++++++++++++++++- readset.py | 5 ++ rssbot.py | 5 ++ 5 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 mirai.py diff --git a/README.md b/README.md index c5b404e..a5a8cd9 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ rssbotLib=rssbot.dll - [botOwnerList](#botownerlist) - [miraiApiHTTPServer](#miraiapihttpserver) - [miraiApiHTTPAuthKey](#miraiapihttpauthkey) +- [miraiApiQQ](#miraiapiqq) ### token 必填参数。Telegram Bot API Token。向[@BotFather](https://t.me/BotFather)请求新建Bot,即可得到。 ### maxCount @@ -60,9 +61,11 @@ rssbotLib=rssbot.dll 特殊权限: - 可以强制更新RSS ### miraiApiHTTPServer -可选参数。指定[mirai-api-http](https://github.com/project-mirai/mirai-api-http)服务器位置。例如`http://localhost:8081`。可用来发送QQ消息。设置后必须设置[`miraiApiHTTPAuthKey`](#miraiapihttpauthkey) +可选参数。指定[mirai-api-http](https://github.com/project-mirai/mirai-api-http)服务器位置。例如`http://localhost:8081`。可用来发送QQ消息。设置后必须设置[`miraiApiHTTPAuthKey`](#miraiapihttpauthkey)和[`miraiApiQQ`](#miraiapiqq) ### miraiApiHTTPAuthKey 可选参数。指定mirai-api-http的[AuthKey](https://github.com/project-mirai/mirai-api-http#开始使用)。 +### miraiApiQQ +可选参数。已在mirai登录的QQ号。 ## 命令行参数 ```text --rebuild-hashlist 重建hashList diff --git a/mirai.py b/mirai.py new file mode 100644 index 0000000..59e8e80 --- /dev/null +++ b/mirai.py @@ -0,0 +1,186 @@ +# (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 requests import Session +from dictdeal import json2data +from urllib.parse import urlencode +from json import dumps +from time import time_ns +from miraiDatabase import MiraiSession, MiraiDatabase +from functools import wraps + + +class LoginRequiredError(Exception): + def __init__(self): + Exception.__init__(self, 'Login is needed.') + + +def login_required(f): + @wraps(f) + def o(*l, **k): + while True: + m: Mirai = l[0] + if m._logined: + db: MiraiDatabase = m._db + v = f(*l, **k) + if v is not None: + if v['code'] > 0 and v['code'] <= 4: + db.removeSession(m._kses.sessionId) + m._logined = False + m.login() + continue + m._kses.lastedUsedTime = m._lastRequestTime + db.setSession(m._kses) + return v + else: + raise LoginRequiredError() + return o + + +class Mirai: + def __init__(self, m): + from rssbot import main + self._m: main = m + self._db = self._m._mriaidb + self._ses = Session() + self._lastRequestTime = 0 + self._logined = False + self.login() + + def _auth(self, authKey: str): + r = self._post("/auth", {"authKey": authKey}) + if r is None: + return None + return r.json() + + def _countMessage(self, sessionKey: str): + r = self._get("/countMessage", {"sessionKey": sessionKey}) + if r is None: + return None + return r.json() + + def _fetchLatestMessage(self, sessionKey: str, count: int): + r = self._get("/fetchLatestMessage", + {"sessionKey": sessionKey, "count": count}) + if r is None: + return None + return r.json() + + def _fetchMessage(self, sessionKey: str, count: int): + r = self._get("/fetchMessage", + {"sessionKey": sessionKey, "count": count}) + if r is None: + return None + return r.json() + + def _get(self, path: str, data: dict = None): + try: + url = f"{self._m._setting.miraiApiHTTPServer}{path}" + p = '' if data is None else urlencode(json2data(data)) + if p != '': + url += '?' + p + r = self._ses.get(url) + self._lastRequestTime = time_ns() + return r + except: + return None + + def _peekMessage(self, sessionKey: str, count: int): + r = self._get("/peekMessage", + {"sessionKey": sessionKey, "count": count}) + if r is None: + return None + return r.json() + + def _post(self, path: str, json: dict, files: dict = None): + try: + url = f"{self._m._setting.miraiApiHTTPServer}{path}" + if files is None: + r = self._ses.post(url, data=dumps(json, ensure_ascii=False, + separators=(',', ':'))) + else: + r = self._ses.post(url, data=json2data(json), files=files) + self._lastRequestTime = time_ns() + return r + except: + return None + + def _release(self, sessionKey: str, qq: int): + r = self._post("/release", {"sessionKey": sessionKey, "qq": qq}) + if r is None: + return None + return r.json() + + def _verify(self, sessionKey: str, qq: int): + r = self._post("/verify", {"sessionKey": sessionKey, "qq": qq}) + if r is None: + return None + return r.json() + + def about(self): + r = self._get("/about") + if r is None: + return None + return r.json() + + @login_required + def countMessage(self): + "获取bot接收并缓存的消息总数,注意不包含被删除的" + return self._countMessage(self._kses.sessionId) + + @login_required + def fetchLatestMessage(self, count: int = 10): + "获取bot接收到的最新消息和最新各类事件(会从MiraiApiHttp消息记录中删除)" + return self._fetchLatestMessage(self._kses.sessionId, count) + + @login_required + def fetchMessage(self, count: int = 10): + "获取bot接收到的最老消息和最老各类事件(会从MiraiApiHttp消息记录中删除)" + return self._fetchMessage(self._kses.sessionId, count) + + def login(self): + ses = self._db.getVerifedSession() + while ses is not None: + r = self._countMessage(ses.sessionId) + if r is not None and r['code'] == 0: + self._kses = ses + ses.lastedUsedTime = self._lastRequestTime + self._db.setSession(ses) + self._logined = True + return True + else: + self._db.removeSession(ses.sessionId) + ses = self._db.getVerifedSession() + r = self._auth(self._m._setting.miraiApiHTTPAuthKey) + if r is None or r['code'] != 0: + return False + ses = MiraiSession(r['session']) + self._db.setSession(ses) + self._kses = ses + r = self._verify(ses.sessionId, self._m._setting.miraiApiQQ) + if r is None or r['code'] != 0: + self._db.removeSession(ses.sessionId) + return False + ses.qq = self._m._setting.miraiApiQQ + ses.lastedUsedTime = self._lastRequestTime + ses.status = 1 + self._db.setSession(ses) + self._logined = True + return True + + @login_required + def peekMessage(self, count: int = 10): + "获取bot接收到的最老消息和最老各类事件(不会从MiraiApiHttp消息记录中删除)" + return self._peekMessage(self._kses.sessionId, count) diff --git a/miraiDatabase.py b/miraiDatabase.py index ae97132..2d2319a 100644 --- a/miraiDatabase.py +++ b/miraiDatabase.py @@ -37,17 +37,62 @@ class MiraiSessionStatus(Enum): class MiraiSession: - def __init__(self, sessionId: str, qq: int, status: Union[MiraiSessionStatus, int], lastedUsedTime: int): + def __init__(self, sessionId: str, qq: int = None, status: Union[MiraiSessionStatus, int] = None, lastedUsedTime: int = None): self._sessionId = sessionId self._qq = qq - if isinstance(status, MiraiSessionStatus): + if status is None: + self._status = MiraiSessionStatus(0) + elif isinstance(status, MiraiSessionStatus): self._status = status - elif isinstance(status, int): + elif isinstance(status, int) and status >= 0 and status <= 2: self._status = MiraiSessionStatus(status) else: self._status = MiraiSessionStatus(0) self._lastedUsedTime = lastedUsedTime + @property + def sessionId(self) -> str: + return self._sessionId + + @property + def qq(self) -> int: + if self._qq is None: + return None + if isinstance(self._qq, int): + return self._qq + else: + raise ValueError('qq must be int.') + + @qq.setter + def qq(self, v): + if isinstance(v, int): + self._qq = v + + @property + def status(self) -> MiraiSessionStatus: + return self._status + + @status.setter + def status(self, v): + if isinstance(v, MiraiSessionStatus): + self._status = v + elif isinstance(v, int) and v >= 0 and v <= 2: + self._status = MiraiSessionStatus(v) + + @property + def lastedUsedTime(self) -> int: + if self._lastedUsedTime is None: + return None + if isinstance(self._lastedUsedTime, int): + return self._lastedUsedTime + else: + raise ValueError('lastedUsedTime must be int.') + + @lastedUsedTime.setter + def lastedUsedTime(self, v): + if isinstance(v, int): + self._lastedUsedTime = v + class MiraiDatabase: def __check_database(self): @@ -110,6 +155,19 @@ class MiraiDatabase: return False return None + def getVerifedSession(self) -> MiraiSession: + self.removeUselessSession() + cur = self._db.execute('SELECT * FROM mirai_session WHERE status=1;') + for i in cur: + return MiraiSession(*i) + return None + + def removeSession(self, sessionId: str): + if self.getSession(sessionId, True): + self._db.execute( + 'DELETE FROM mirai_session WHERE sessionId=?;', (sessionId,)) + self._db.commit() + def removeUselessSession(self): with self._lock: try: @@ -120,3 +178,13 @@ class MiraiDatabase: return True except: return False + + def setSession(self, ses: MiraiSession): + if self.getSession(ses.sessionId, True): + self._db.execute( + 'UPDATE mirai_session SET qq=?, status=?, lastedUsedTime=? WHERE sessionId=?;', + (ses.qq, ses.status.value, ses.lastedUsedTime, ses.sessionId)) + else: + self._db.execute('INSERT INTO mirai_session VALUES (?, ?, ?, ?);', + (ses.sessionId, ses.qq, ses.status.value, ses.lastedUsedTime)) + self._db.commit() diff --git a/readset.py b/readset.py index 48101f8..1105b5f 100644 --- a/readset.py +++ b/readset.py @@ -56,6 +56,7 @@ class settings: self._main, d['botOwnerList']) if 'botOwnerList' in d and d['botOwnerList'] != '' else BotOwnerList(self._main) self._miraiApiHTTPServer = d['miraiApiHTTPServer'] if 'miraiApiHTTPServer' in d and d['miraiApiHTTPServer'] != '' else None self._miraiApiHTTPAuthKey = d['miraiApiHTTPAuthKey'] if 'miraiApiHTTPAuthKey' in d and d['miraiApiHTTPAuthKey'] != '' else None + self._miraiApiQQ = int(d['miraiApiQQ']) if 'miraiApiQQ' in d and d['miraiApiQQ'].isnumeric() else None @property def token(self) -> str: @@ -113,6 +114,10 @@ class settings: def miraiApiHTTPAuthKey(self) -> str: return self._miraiApiHTTPAuthKey + @property + def miraiApiQQ(self) -> int: + return self._miraiApiQQ + class commandline: def __init__(self, commandline: List[str] = None): diff --git a/rssbot.py b/rssbot.py index 33d266b..429888d 100644 --- a/rssbot.py +++ b/rssbot.py @@ -38,6 +38,7 @@ from dictdeal import json2data from rssbotlib import loadRSSBotLib, AddVideoInfoResult from time import sleep from miraiDatabase import MiraiDatabase +from mirai import Mirai MAX_ITEM_IN_MEDIA_GROUP = 10 @@ -587,7 +588,11 @@ class main: if self._setting.miraiApiHTTPAuthKey is None: print('未设置AuthKey。') return -1 + if self._setting.miraiApiQQ is None: + print('未设置QQ号。') + return -1 self._mriaidb = MiraiDatabase(self, self._setting.databaseLocation) + self._mriai = Mirai(self) self._r = Session() if self._telegramBotApiServer != 'https://api.telegram.org': self._request("logOut", "post",