misskey2telegram/misskey_init.py

288 lines
11 KiB
Python
Raw Normal View History

2023-01-27 12:36:41 +00:00
import contextlib
2023-07-21 14:00:49 +00:00
from asyncio import sleep, Lock
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
from mipa.exception import WebSocketNotConnected
2022-12-22 14:18:43 +00:00
from mipa.ext import commands
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
NotificationNote,
2023-08-07 06:42:39 +00:00
Route,
2023-07-03 14:39:52 +00:00
)
2023-01-27 12:36:41 +00:00
from mipac.client import Client as MisskeyClient
2023-08-10 03:53:28 +00:00
from pyrogram.errors import ChatWriteForbidden
from pyrogram.types import Message
2022-12-22 14:18:43 +00:00
2023-08-10 03:53:28 +00:00
from defs.misskey import send_update, send_notice
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,
send_note_mention,
2023-07-03 14:39:52 +00:00
)
2023-01-27 12:36:41 +00:00
from models.models.user import User, TokenStatusEnum
2023-08-09 07:10:54 +00:00
from models.models.user_config import UserConfig
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
2023-08-09 07:10:54 +00:00
from models.services.user_config import UserConfigAction
2022-12-22 14:18:43 +00:00
class MisskeyBot(commands.Bot):
2023-08-09 07:10:54 +00:00
def __init__(self, user: User, user_config: UserConfig):
2022-12-22 14:18:43 +00:00
super().__init__()
2023-08-07 12:48:02 +00:00
self._BotBase__on_error = self.__on_error
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-08-09 07:10:54 +00:00
self.user_config: UserConfig = user_config
2023-07-21 14:00:49 +00:00
self.lock = Lock()
2022-12-22 14:18:43 +00:00
2023-08-07 06:42:39 +00:00
async def fetch_offline_notes(self):
logs.info(f"{self.tg_user.user_id} 开始获取最近十条时间线")
2024-02-19 03:57:41 +00:00
data = {"withReplies": False, "limit": 10}
2023-08-07 06:42:39 +00:00
data = await self.core.http.request(
Route("POST", "/api/notes/timeline"), auth=True, json=data
)
2024-02-18 15:46:14 +00:00
for note in (Note(raw_note=note, client=self.client) for note in data):
2023-08-07 06:42:39 +00:00
await self.process_note(note, notice=False)
logs.info(f"{self.tg_user.user_id} 处理完成最近十条时间线")
2024-02-19 03:26:00 +00:00
async def when_start(self, _):
await self._router.connect_channel(["main", "home"])
2024-09-30 13:59:28 +00:00
# await self.fetch_offline_notes()
# subs = await RevokeAction.get_all_subs(self.tg_user.user_id)
# for sub in subs:
# await self._router.capture_message(sub)
2023-07-21 14:00:49 +00:00
async def on_ready(self, ws):
2023-08-08 04:28:18 +00:00
try:
await self.when_start(ws)
logs.info(f"成功启动 Misskey Bot WS 任务 {self.user_id}")
except ConnectionResetError:
"""在预启动时WS 已被关闭"""
2022-12-22 14:18:43 +00:00
2022-12-24 04:09:40 +00:00
async def on_reconnect(self, ws):
2023-08-08 04:28:18 +00:00
try:
await self.when_start(ws)
logs.info(f"成功重连 Misskey Bot WS 任务 {self.user_id}")
except ConnectionResetError:
"""在预启动时WS 已被关闭"""
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
2023-08-10 03:53:28 +00:00
async def send_update(self, note: Note, send_type: str) -> Message | list[Message]:
cid = (
self.tg_user.chat_id
if send_type == "timeline"
else self.tg_user.push_chat_id
)
try:
if send_type == "timeline":
return await send_update(
self.tg_user.host,
self.tg_user.chat_id,
note,
self.tg_user.timeline_topic,
True,
spoiler=self.user_config and self.user_config.timeline_spoiler,
)
else:
return await send_update(
self.tg_user.host,
self.tg_user.push_chat_id,
note,
None,
False,
spoiler=self.user_config and self.user_config.push_spoiler,
)
except ChatWriteForbidden:
logs.warning(f"{self.tg_user.user_id} 无法向 {send_type} {cid} 发送消息")
if send_type == "timeline":
await UserAction.change_user_group_id(self.tg_user.user_id, 0)
else:
await UserAction.change_user_push(self.tg_user.user_id, 0)
await send_notice(self.tg_user.user_id, f"无法向 {cid} 发送消息,已停止推送")
await rerun_misskey_bot(self.tg_user.user_id)
2023-08-07 06:42:39 +00:00
async def process_note(self, note: Note, notice: bool = True):
2023-07-21 14:00:49 +00:00
async with self.lock:
2023-08-08 04:28:18 +00:00
try:
if await NoRepeatRenoteAction.check(self.tg_user.user_id, note):
if self.tg_user.chat_id != 0 and self.tg_user.timeline_topic != 0:
2023-08-10 03:53:28 +00:00
msgs = await self.send_update(note, "timeline")
2023-08-08 04:28:18 +00:00
await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
if self.check_push(note):
2023-08-10 03:53:28 +00:00
msgs = await self.send_update(note, "push")
2023-08-08 04:28:18 +00:00
await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
elif notice:
logs.info(f"{self.tg_user.user_id} 跳过重复转发 note {note.id}")
await NoRepeatRenoteAction.set(self.tg_user.user_id, note)
except Exception:
logs.exception(
f"{self.tg_user.user_id} 处理 note {self.tg_user.host}/notes/{note.id} 发生异常"
)
2023-08-07 06:42:39 +00:00
async def on_note(self, note: Note):
logs.info(f"{self.tg_user.user_id} 收到新 note {note.id}")
await self.process_note(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} 完成")
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.host,
self.tg_user.chat_id,
notice,
self.tg_user.notice_topic,
2023-08-03 09:10:17 +00:00
)
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.host,
self.tg_user.chat_id,
notice,
self.tg_user.notice_topic,
2023-08-03 09:10:17 +00:00
)
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.host,
self.tg_user.chat_id,
notice,
self.tg_user.notice_topic,
2023-08-03 09:10:17 +00:00
)
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.host,
self.tg_user.chat_id,
notice,
self.tg_user.notice_topic,
2023-08-03 09:10:17 +00:00
)
2024-02-22 06:16:06 +00:00
@staticmethod
def ignore_mention(note: NotificationNote) -> bool:
new_note = note.note
if len(new_note.mentions) >= 3 and \
new_note.user.username and len(new_note.user.username) == 10:
return True
return False
2023-08-03 09:10:17 +00:00
async def on_mention(self, notice: NotificationNote):
if self.tg_user.chat_id != 0 and self.tg_user.notice_topic != 0:
2024-02-22 06:16:06 +00:00
if self.ignore_mention(notice):
logs.warning(f"{self.tg_user.user_id} 遇到 spam 轰炸 {notice.note.id}")
return
2024-02-19 03:26:00 +00:00
msg = await send_note_mention(
self.tg_user.host,
self.tg_user.chat_id,
notice,
self.tg_user.notice_topic,
)
2024-02-19 03:26:00 +00:00
await RevokeAction.push_extend(self.tg_user.user_id, notice.note.id, msg)
await self._router.capture_message(notice.note.id)
2023-01-27 12:36:41 +00:00
2023-08-07 12:48:02 +00:00
@staticmethod
async def __on_error(event_method: str) -> None:
logs.exception(f"MisskeyBot 执行 {event_method} 出错", exc_info=True)
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-08-09 07:10:54 +00:00
async def create_or_get_misskey_bot(user: User, user_config: UserConfig) -> MisskeyBot:
2023-07-03 14:22:02 +00:00
if user.user_id not in misskey_bot_map:
2023-08-09 07:10:54 +00:00
misskey_bot_map[user.user_id] = MisskeyBot(user, user_config)
2023-07-03 14:22:02 +00:00
return misskey_bot_map[user.user_id]
2023-01-27 12:36:41 +00:00
async def run(user: User):
2023-08-09 07:10:54 +00:00
user_config = await UserConfigAction.get_user_config_by_id(user.user_id)
misskey = await create_or_get_misskey_bot(user, user_config)
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 任务")