2023-08-03 09:10:17 +00:00
|
|
|
import asyncio
|
2023-01-27 12:36:41 +00:00
|
|
|
import contextlib
|
2023-07-21 14:00:49 +00:00
|
|
|
from asyncio import sleep, Lock
|
2023-08-03 09:10:17 +00:00
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
2023-07-20 15:35:10 +00:00
|
|
|
from typing import Optional, Union
|
2023-01-27 12:36:41 +00:00
|
|
|
|
|
|
|
from aiohttp import ClientConnectorError
|
2023-08-03 09:10:17 +00:00
|
|
|
from firebase_admin.exceptions import InvalidArgumentError
|
2023-01-27 12:36:41 +00:00
|
|
|
from mipa.exception import WebSocketNotConnected
|
2022-12-22 14:18:43 +00:00
|
|
|
from mipa.ext import commands
|
|
|
|
from mipa.router import Router
|
2023-07-03 14:39:52 +00:00
|
|
|
from mipac import (
|
|
|
|
Note,
|
|
|
|
NotificationFollow,
|
|
|
|
NotificationFollowRequest,
|
|
|
|
NotificationAchievement,
|
2023-07-21 14:00:49 +00:00
|
|
|
NoteDeleted,
|
2023-08-03 09:10:17 +00:00
|
|
|
NotificationReaction,
|
|
|
|
NotificationNote,
|
2023-07-03 14:39:52 +00:00
|
|
|
)
|
2023-01-27 12:36:41 +00:00
|
|
|
from mipac.client import Client as MisskeyClient
|
2022-12-22 14:18:43 +00:00
|
|
|
|
2023-08-03 09:10:17 +00:00
|
|
|
from defs.fcm_notice import (
|
|
|
|
send_fcm_user_followed,
|
|
|
|
send_fcm_follow_request,
|
|
|
|
send_fcm_follow_request_accept,
|
|
|
|
send_fcm_achievement_earned,
|
|
|
|
send_fcm_reaction,
|
|
|
|
send_fcm_mention,
|
|
|
|
send_fcm_reply,
|
|
|
|
send_fcm_renote,
|
|
|
|
send_fcm_quote,
|
|
|
|
)
|
2022-12-22 14:18:43 +00:00
|
|
|
from defs.misskey import send_update
|
2023-07-03 14:39:52 +00:00
|
|
|
from defs.notice import (
|
|
|
|
send_user_followed,
|
|
|
|
send_follow_request,
|
|
|
|
send_follow_request_accept,
|
|
|
|
send_achievement_earned,
|
|
|
|
)
|
2023-01-27 12:36:41 +00:00
|
|
|
|
|
|
|
from models.models.user import User, TokenStatusEnum
|
2023-07-29 05:46:57 +00:00
|
|
|
from models.services.no_repeat_renote import NoRepeatRenoteAction
|
2023-07-21 14:00:49 +00:00
|
|
|
from models.services.revoke import RevokeAction
|
2023-01-27 12:36:41 +00:00
|
|
|
from models.services.user import UserAction
|
|
|
|
|
|
|
|
from init import bot, logs, sqlite
|
2022-12-22 14:18:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class MisskeyBot(commands.Bot):
|
2023-07-03 14:22:02 +00:00
|
|
|
def __init__(self, user: User):
|
2022-12-22 14:18:43 +00:00
|
|
|
super().__init__()
|
2023-07-03 14:22:02 +00:00
|
|
|
self.user_id: int = user.user_id
|
2023-07-20 15:35:10 +00:00
|
|
|
self.instance_user_id: str = user.instance_user_id
|
2023-07-03 14:22:02 +00:00
|
|
|
self.tg_user: User = user
|
2023-07-21 14:00:49 +00:00
|
|
|
self.lock = Lock()
|
2022-12-22 14:18:43 +00:00
|
|
|
|
2023-07-21 14:00:49 +00:00
|
|
|
async def when_start(self, ws):
|
2022-12-23 03:06:17 +00:00
|
|
|
await Router(ws).connect_channel(["main", "home"])
|
2023-07-21 14:00:49 +00:00
|
|
|
subs = await RevokeAction.get_all_subs(self.tg_user.user_id)
|
|
|
|
for sub in subs:
|
|
|
|
await Router(ws).capture_message(sub)
|
|
|
|
|
|
|
|
async def on_ready(self, ws):
|
|
|
|
await self.when_start(ws)
|
2023-01-27 12:36:41 +00:00
|
|
|
logs.info(f"成功启动 Misskey Bot WS 任务 {self.user_id}")
|
2022-12-22 14:18:43 +00:00
|
|
|
|
2022-12-24 04:09:40 +00:00
|
|
|
async def on_reconnect(self, ws):
|
2023-07-21 14:00:49 +00:00
|
|
|
await self.when_start(ws)
|
|
|
|
logs.info(f"成功重连 Misskey Bot WS 任务 {self.user_id}")
|
2022-12-24 04:09:40 +00:00
|
|
|
|
2023-07-23 02:39:38 +00:00
|
|
|
def check_push(self, note: Note):
|
|
|
|
if note.user_id != self.instance_user_id:
|
|
|
|
return False
|
|
|
|
if self.tg_user.push_chat_id == 0:
|
|
|
|
return False
|
|
|
|
if note.visibility in ["specified"]:
|
|
|
|
return False
|
2023-07-23 03:51:22 +00:00
|
|
|
if "nofwd" in note.tags:
|
2023-07-23 02:39:38 +00:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2022-12-24 13:19:24 +00:00
|
|
|
async def on_note(self, note: Note):
|
2023-07-21 05:56:57 +00:00
|
|
|
logs.info(f"{self.tg_user.user_id} 收到新 note {note.id}")
|
2023-07-21 14:00:49 +00:00
|
|
|
async with self.lock:
|
2023-07-29 05:46:57 +00:00
|
|
|
if await NoRepeatRenoteAction.check(self.tg_user.user_id, note):
|
|
|
|
if self.tg_user.chat_id != 0 and self.tg_user.timeline_topic != 0:
|
|
|
|
msgs = await send_update(
|
|
|
|
self.tg_user.host,
|
|
|
|
self.tg_user.chat_id,
|
|
|
|
note,
|
|
|
|
self.tg_user.timeline_topic,
|
|
|
|
True,
|
|
|
|
)
|
|
|
|
await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
|
|
|
|
if self.check_push(note):
|
|
|
|
msgs = await send_update(
|
|
|
|
self.tg_user.host, self.tg_user.push_chat_id, note, None, False
|
|
|
|
)
|
|
|
|
await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
|
2023-07-29 05:51:14 +00:00
|
|
|
else:
|
|
|
|
logs.info(f"{self.tg_user.user_id} 跳过重复转发 note {note.id}")
|
2023-07-29 05:46:57 +00:00
|
|
|
await NoRepeatRenoteAction.set(self.tg_user.user_id, note)
|
2023-07-21 05:56:57 +00:00
|
|
|
logs.info(f"{self.tg_user.user_id} 处理 note {note.id} 完成")
|
2022-12-22 14:18:43 +00:00
|
|
|
|
2023-07-21 14:00:49 +00:00
|
|
|
async def on_note_deleted(self, note: NoteDeleted):
|
|
|
|
logs.info(f"{self.tg_user.user_id} 收到 note 删除 {note.note_id}")
|
|
|
|
async with self.lock:
|
|
|
|
await RevokeAction.process_delete_note(self.tg_user.user_id, note.note_id)
|
|
|
|
logs.info(f"{self.tg_user.user_id} 处理 note 删除 {note.note_id} 完成")
|
|
|
|
|
2023-08-03 09:20:21 +00:00
|
|
|
async def send_fcm(self, func, notice):
|
2023-08-03 09:10:17 +00:00
|
|
|
logs.info(f"{self.tg_user.user_id} 发送 FCM 通知 {func.__name__}")
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
try:
|
|
|
|
with ThreadPoolExecutor() as executor:
|
|
|
|
await loop.run_in_executor(
|
|
|
|
executor, func, self.tg_user.fcm_token, notice
|
|
|
|
)
|
|
|
|
except InvalidArgumentError:
|
|
|
|
logs.error(f"{self.tg_user.user_id} 无效的 FCM Token")
|
|
|
|
except Exception as e:
|
|
|
|
logs.error(e)
|
|
|
|
|
2022-12-26 09:48:13 +00:00
|
|
|
async def on_user_followed(self, notice: NotificationFollow):
|
2023-08-03 09:10:17 +00:00
|
|
|
if self.tg_user.chat_id != 0 and self.tg_user.notice_topic != 0:
|
|
|
|
await send_user_followed(
|
|
|
|
self.tg_user.chat_id, notice, self.tg_user.notice_topic
|
|
|
|
)
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_user_followed, notice)
|
2022-12-26 09:48:13 +00:00
|
|
|
|
|
|
|
async def on_follow_request(self, notice: NotificationFollowRequest):
|
2023-08-03 09:10:17 +00:00
|
|
|
if self.tg_user.chat_id != 0 and self.tg_user.notice_topic != 0:
|
|
|
|
await send_follow_request(
|
|
|
|
self.tg_user.chat_id, notice, self.tg_user.notice_topic
|
|
|
|
)
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_follow_request, notice)
|
2022-12-26 09:48:13 +00:00
|
|
|
|
|
|
|
async def on_follow_request_accept(self, notice: NotificationFollowRequest):
|
2023-08-03 09:10:17 +00:00
|
|
|
if self.tg_user.chat_id != 0 and self.tg_user.notice_topic != 0:
|
|
|
|
await send_follow_request_accept(
|
|
|
|
self.tg_user.chat_id, notice, self.tg_user.notice_topic
|
|
|
|
)
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_follow_request_accept, notice)
|
2023-01-17 17:11:26 +00:00
|
|
|
|
2023-01-24 07:23:25 +00:00
|
|
|
async def on_achievement_earned(self, notice: NotificationAchievement):
|
2023-08-03 09:10:17 +00:00
|
|
|
if self.tg_user.chat_id != 0 and self.tg_user.notice_topic != 0:
|
|
|
|
await send_achievement_earned(
|
|
|
|
self.tg_user.chat_id, notice, self.tg_user.notice_topic
|
|
|
|
)
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_achievement_earned, notice)
|
|
|
|
|
|
|
|
async def on_reaction(self, notice: NotificationReaction):
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_reaction, notice)
|
|
|
|
|
|
|
|
async def on_mention(self, notice: NotificationNote):
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_mention, notice)
|
|
|
|
|
|
|
|
async def on_reply(self, notice: NotificationNote):
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_reply, notice)
|
|
|
|
|
|
|
|
async def on_renote(self, notice: NotificationNote):
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_renote, notice)
|
|
|
|
|
|
|
|
async def on_quote(self, notice: NotificationNote):
|
|
|
|
if self.tg_user.fcm_token:
|
|
|
|
await self.send_fcm(send_fcm_quote, notice)
|
2023-01-27 12:36:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
misskey_bot_map: dict[int, MisskeyBot] = {}
|
|
|
|
|
|
|
|
|
|
|
|
def get_misskey_bot(user_id: int) -> Optional[MisskeyBot]:
|
|
|
|
return None if user_id not in misskey_bot_map else misskey_bot_map[user_id]
|
|
|
|
|
|
|
|
|
2023-07-03 14:22:02 +00:00
|
|
|
async def create_or_get_misskey_bot(user: User) -> MisskeyBot:
|
|
|
|
if user.user_id not in misskey_bot_map:
|
|
|
|
misskey_bot_map[user.user_id] = MisskeyBot(user)
|
|
|
|
return misskey_bot_map[user.user_id]
|
2023-01-27 12:36:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def run(user: User):
|
2023-07-03 14:22:02 +00:00
|
|
|
misskey = await create_or_get_misskey_bot(user)
|
2023-01-27 12:36:41 +00:00
|
|
|
try:
|
|
|
|
logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}")
|
2023-07-21 10:26:12 +00:00
|
|
|
await misskey.start(f"wss://{user.host}/streaming", user.token, log_level=None)
|
2023-01-27 12:36:41 +00:00
|
|
|
except ClientConnectorError:
|
2023-07-21 05:56:57 +00:00
|
|
|
logs.warning(f"Misskey Bot WS 任务 {user.user_id} 掉线重连")
|
2023-01-27 12:36:41 +00:00
|
|
|
await sleep(3)
|
2023-07-21 05:39:41 +00:00
|
|
|
bot.loop.create_task(run(user))
|
2023-01-27 12:36:41 +00:00
|
|
|
|
|
|
|
|
2023-07-20 15:35:10 +00:00
|
|
|
async def test_token(host: str, token: str) -> Union[str, bool]:
|
2023-01-27 12:36:41 +00:00
|
|
|
try:
|
2023-07-20 14:21:37 +00:00
|
|
|
logs.info(f"验证 Token {host} {token}")
|
2023-07-21 10:26:12 +00:00
|
|
|
client = MisskeyClient(f"https://{host}", token, log_level=None)
|
2023-01-27 12:36:41 +00:00
|
|
|
await client.http.login()
|
2023-07-20 15:35:10 +00:00
|
|
|
me = await client.api.user.action.get_me()
|
2023-01-27 12:36:41 +00:00
|
|
|
await client.http.close_session()
|
2023-07-20 15:35:10 +00:00
|
|
|
return me.id
|
2023-01-27 12:36:41 +00:00
|
|
|
except Exception:
|
2023-07-21 05:56:57 +00:00
|
|
|
logs.warning(f"Token {host} {token} 验证失败")
|
2023-01-27 12:36:41 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
async def rerun_misskey_bot(user_id: int) -> bool:
|
|
|
|
if misskey := get_misskey_bot(user_id):
|
|
|
|
with contextlib.suppress(WebSocketNotConnected):
|
|
|
|
await misskey.disconnect()
|
|
|
|
misskey_bot_map.pop(user_id)
|
|
|
|
user = await UserAction.get_user_if_ok(user_id)
|
|
|
|
if not user:
|
|
|
|
return False
|
2023-07-20 15:35:10 +00:00
|
|
|
mid = await test_token(user.host, user.token)
|
|
|
|
if not mid:
|
2023-01-27 12:36:41 +00:00
|
|
|
await UserAction.set_user_status(user_id, TokenStatusEnum.INVALID_TOKEN)
|
|
|
|
return False
|
2023-07-20 15:35:10 +00:00
|
|
|
user.instance_user_id = mid
|
|
|
|
await UserAction.change_instance_user_id(user_id, mid)
|
2023-01-27 12:36:41 +00:00
|
|
|
bot.loop.create_task(run(user))
|
|
|
|
return True
|
2023-01-24 07:23:25 +00:00
|
|
|
|
2022-12-22 14:18:43 +00:00
|
|
|
|
2023-01-27 12:36:41 +00:00
|
|
|
async def init_misskey_bot():
|
|
|
|
await sqlite.create_db_and_tables()
|
|
|
|
count = 0
|
2023-07-24 12:03:44 +00:00
|
|
|
for user in await UserAction.get_all_have_token_users():
|
2023-07-20 15:54:58 +00:00
|
|
|
mid = await test_token(user.host, user.token)
|
|
|
|
if not mid:
|
2023-07-21 05:56:57 +00:00
|
|
|
logs.warning(f"{user.user_id} Token 失效")
|
2023-01-27 12:36:41 +00:00
|
|
|
user.status = TokenStatusEnum.INVALID_TOKEN
|
|
|
|
await UserAction.update_user(user)
|
|
|
|
continue
|
2023-07-20 15:54:58 +00:00
|
|
|
user.instance_user_id = mid
|
2023-01-27 12:36:41 +00:00
|
|
|
count += 1
|
|
|
|
bot.loop.create_task(run(user))
|
|
|
|
logs.info(f"初始化 Misskey Bot 完成,共启动 {count} 个 WS 任务")
|