diff --git a/defs/chat.py b/defs/chat.py index 04469e0..4fbe36d 100644 --- a/defs/chat.py +++ b/defs/chat.py @@ -5,28 +5,27 @@ from mipac.models.lite import LiteUser from pyrogram.errors import MediaEmpty from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from glover import misskey_host from init import bot, request from models.services.scheduler import add_delete_file_job, delete_file -def get_user_link(user: LiteUser) -> str: +def get_user_link(host: str, user: LiteUser) -> str: if user.host: - return f"https://{user.host}/@{user.username}" - return f"{misskey_host}/@{user.username}" + return f"https://{host}/@{user.username}@{user.host}" + return f"https://{host}/@{user.username}" -def get_source_link(message: ChatMessage) -> str: +def get_source_link(host: str, message: ChatMessage) -> str: return ( - f"{misskey_host}/my/messaging/{message.user.username}?cid={message.user.id}" + f"https://{host}/my/messaging/{message.user.username}?cid={message.user.id}" if not message.group and message.user - else f"{misskey_host}/my/messaging/group/{message.group.id}" + else f"https://{host}/my/messaging/group/{message.group.id}" ) -def gen_button(message: ChatMessage): - author = get_user_link(message.user) - source = get_source_link(message) +def gen_button(host: str, message: ChatMessage): + author = get_user_link(host, message.user) + source = get_source_link(host, message) first_line = [ InlineKeyboardButton(text="Chat", url=source), InlineKeyboardButton(text="Author", url=author), @@ -34,24 +33,26 @@ def gen_button(message: ChatMessage): return InlineKeyboardMarkup([first_line]) -def get_content(message: ChatMessage) -> str: +def get_content(host: str, message: ChatMessage) -> str: content = message.text or "" content = content[:768] - user = f'{message.user.nickname}' + user = f'{message.user.nickname}' if message.group: - group = f'{message.group.name}' + group = f'{message.group.name}' user += f" ( {group} )" return f"""Misskey Message {user}: {content}""" -async def send_text(cid: int, message: ChatMessage, reply_to_message_id: int): +async def send_text( + host: str, cid: int, message: ChatMessage, reply_to_message_id: int +): await bot.send_message( cid, - get_content(message), + get_content(host, message), reply_to_message_id=reply_to_message_id, - reply_markup=gen_button(message), + reply_markup=gen_button(host, message), disable_web_page_preview=True, ) @@ -61,53 +62,53 @@ def deprecated_to_text(func): try: return await func(*args, **kwargs) except MediaEmpty: - return await send_text(args[0], args[2], args[3]) + return await send_text(args[0], args[1], args[3], args[4]) return wrapper @deprecated_to_text async def send_photo( - cid: int, url: str, message: ChatMessage, reply_to_message_id: int + host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int ): if not url: - return await send_text(cid, message, reply_to_message_id) + return await send_text(host, cid, message, reply_to_message_id) await bot.send_photo( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(message), - reply_markup=gen_button(message), + caption=get_content(host, message), + reply_markup=gen_button(host, message), ) @deprecated_to_text async def send_video( - cid: int, url: str, message: ChatMessage, reply_to_message_id: int + host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int ): if not url: - return await send_text(cid, message, reply_to_message_id) + return await send_text(host, cid, message, reply_to_message_id) await bot.send_video( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(message), - reply_markup=gen_button(message), + caption=get_content(host, message), + reply_markup=gen_button(host, message), ) @deprecated_to_text async def send_audio( - cid: int, url: str, message: ChatMessage, reply_to_message_id: int + host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int ): if not url: - return await send_text(cid, message, reply_to_message_id) + return await send_text(host, cid, message, reply_to_message_id) await bot.send_audio( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(message), - reply_markup=gen_button(message), + caption=get_content(host, message), + reply_markup=gen_button(host, message), ) @@ -129,31 +130,31 @@ async def fetch_document(file: File) -> Optional[str]: @deprecated_to_text async def send_document( - cid: int, file: File, message: ChatMessage, reply_to_message_id: int + host: str, cid: int, file: File, message: ChatMessage, reply_to_message_id: int ): file = await fetch_document(file) if not file: - return await send_text(cid, message, reply_to_message_id) + return await send_text(host, cid, message, reply_to_message_id) await bot.send_document( cid, file, reply_to_message_id=reply_to_message_id, - caption=get_content(message), - reply_markup=gen_button(message), + caption=get_content(host, message), + reply_markup=gen_button(host, message), ) await delete_file(file) -async def send_chat_message(cid: int, message: ChatMessage, topic_id: int): +async def send_chat_message(host: str, cid: int, message: ChatMessage, topic_id: int): if not message.file: - return await send_text(cid, message, topic_id) + return await send_text(host, cid, message, topic_id) file_url = message.file.url file_type = message.file.type if file_type.startswith("image"): - await send_photo(cid, file_url, message, topic_id) + await send_photo(host, cid, file_url, message, topic_id) elif file_type.startswith("video"): - await send_video(cid, file_url, message, topic_id) + await send_video(host, cid, file_url, message, topic_id) elif file_type.startswith("audio"): - await send_audio(cid, file_url, message, topic_id) + await send_audio(host, cid, file_url, message, topic_id) else: - await send_document(cid, message.file, message, topic_id) + await send_document(host, cid, message.file, message, topic_id) diff --git a/defs/check_node.py b/defs/check_node.py new file mode 100644 index 0000000..de6abfd --- /dev/null +++ b/defs/check_node.py @@ -0,0 +1,30 @@ +from httpx import URL, InvalidURL + +from init import request + + +def get_host(url: str) -> str: + try: + url = URL(url) + except InvalidURL: + return "" + return url.host + + +async def check_host(host: str) -> bool: + if not host: + return False + try: + req = await request.get(f"https://{host}/.well-known/nodeinfo") + req.raise_for_status() + node_url = req.json()["links"][0]["href"] + req = await request.get(node_url) + req.raise_for_status() + data = req.json() + if data["software"]["name"] != "misskey": + raise ValueError + if not data["software"]["version"].startswith("13."): + raise ValueError + return True + except Exception: + return False diff --git a/defs/misskey.py b/defs/misskey.py index a88b85e..c427799 100644 --- a/defs/misskey.py +++ b/defs/misskey.py @@ -6,7 +6,6 @@ from mipac.models.lite import LiteUser from mipac.types import IDriveFile from pyrogram.enums import ParseMode from pyrogram.errors import MediaEmpty - from pyrogram.types import ( InlineKeyboardMarkup, InlineKeyboardButton, @@ -16,18 +15,17 @@ from pyrogram.types import ( InputMediaAudio, ) -from glover import misskey_host from init import bot, request from models.services.scheduler import add_delete_file_job, delete_file -def get_note_url(note: Note) -> str: - return f"{misskey_host}/notes/{note.id}" +def get_note_url(host: str, note: Note) -> str: + return f"https://{host}/notes/{note.id}" -def gen_button(note: Note, author: str): - source = get_note_url(note) - reply_source = get_note_url(note.reply) if note.reply else None +def gen_button(host: str, note: Note, author: str): + source = get_note_url(host, note) + reply_source = get_note_url(host, note.reply) if note.reply else None renote_id = note.renote_id if note.reply else note.id if reply_source: first_line = [ @@ -48,14 +46,16 @@ def gen_button(note: Note, author: str): return InlineKeyboardMarkup([first_line, second_line]) -def get_user_link(user: LiteUser) -> str: +def get_user_link(host: str, user: LiteUser) -> str: if user.host: - return f"https://{user.host}/@{user.username}" - return f"{misskey_host}/@{user.username}" + return f"https://{host}/@{user.username}@{user.host}" + return f"https://{host}/@{user.username}" -def get_user_alink(user: LiteUser) -> str: - return "{}".format(get_user_link(user), user.nickname or f"@{user.username}") +def get_user_alink(host: str, user: LiteUser) -> str: + return '{}'.format( + get_user_link(host, user), user.nickname or f"@{user.username}" + ) def get_post_time(date: datetime) -> str: @@ -66,7 +66,7 @@ def get_post_time(date: datetime) -> str: return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") -def get_content(note: Note) -> str: +def get_content(host: str, note: Note) -> str: content = note.content or "" action = "发表" origin = "" @@ -76,7 +76,7 @@ def get_content(note: Note) -> str: action = "转推" content = note.renote.content or content origin = ( - f'\n{get_user_alink(note.renote.author)} ' + f"\n{get_user_alink(host, note.renote.author)} " f"发表于 {get_post_time(note.renote.created_at)}" ) content = content[:768] @@ -84,16 +84,16 @@ def get_content(note: Note) -> str: {content} -{get_user_alink(note.author)} {action}于 {get_post_time(note.created_at)}{origin} +{get_user_alink(host, note.author)} {action}于 {get_post_time(note.created_at)}{origin} 点赞: {sum(show_note.reactions.values())} | 回复: {show_note.replies_count} | 转发: {show_note.renote_count}""" -async def send_text(cid: int, note: Note, reply_to_message_id: int): +async def send_text(host: str, cid: int, note: Note, reply_to_message_id: int): await bot.send_message( cid, - get_content(note), + get_content(host, note), reply_to_message_id=reply_to_message_id, - reply_markup=gen_button(note, get_user_link(note.author)), + reply_markup=gen_button(host, note, get_user_link(host, note.author)), disable_web_page_preview=True, ) @@ -103,47 +103,53 @@ def deprecated_to_text(func): try: return await func(*args, **kwargs) except MediaEmpty: - return await send_text(args[0], args[2], args[3]) + return await send_text(args[0], args[1], args[3], args[4]) return wrapper @deprecated_to_text -async def send_photo(cid: int, url: str, note: Note, reply_to_message_id: int): +async def send_photo( + host: str, cid: int, url: str, note: Note, reply_to_message_id: int +): if not url: - return await send_text(cid, note, reply_to_message_id) + return await send_text(host, cid, note, reply_to_message_id) await bot.send_photo( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(note), - reply_markup=gen_button(note, get_user_link(note.author)), + caption=get_content(host, note), + reply_markup=gen_button(host, note, get_user_link(host, note.author)), ) @deprecated_to_text -async def send_video(cid: int, url: str, note: Note, reply_to_message_id: int): +async def send_video( + host: str, cid: int, url: str, note: Note, reply_to_message_id: int +): if not url: - return await send_text(cid, note, reply_to_message_id) + return await send_text(host, cid, note, reply_to_message_id) await bot.send_video( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(note), - reply_markup=gen_button(note, get_user_link(note.author)), + caption=get_content(host, note), + reply_markup=gen_button(host, note, get_user_link(host, note.author)), ) @deprecated_to_text -async def send_audio(cid: int, url: str, note: Note, reply_to_message_id: int): +async def send_audio( + host: str, cid: int, url: str, note: Note, reply_to_message_id: int +): if not url: - return await send_text(cid, note, reply_to_message_id) + return await send_text(host, cid, note, reply_to_message_id) await bot.send_audio( cid, url, reply_to_message_id=reply_to_message_id, - caption=get_content(note), - reply_markup=gen_button(note, get_user_link(note.author)), + caption=get_content(host, note), + reply_markup=gen_button(host, note, get_user_link(host, note.author)), ) @@ -165,17 +171,17 @@ async def fetch_document(file: IDriveFile) -> Optional[str]: @deprecated_to_text async def send_document( - cid: int, file: IDriveFile, note: Note, reply_to_message_id: int + host: str, cid: int, file: IDriveFile, note: Note, reply_to_message_id: int ): file = await fetch_document(file) if not file: - return await send_text(cid, note, reply_to_message_id) + return await send_text(host, cid, note, reply_to_message_id) await bot.send_document( cid, file, reply_to_message_id=reply_to_message_id, - caption=get_content(note), - reply_markup=gen_button(note, get_user_link(note.author)), + caption=get_content(host, note), + reply_markup=gen_button(host, note, get_user_link(host, note.author)), ) await delete_file(file) @@ -219,11 +225,11 @@ async def get_media_group(files: list[IDriveFile]) -> list: async def send_group( - cid: int, files: list[IDriveFile], note: Note, reply_to_message_id: int + host: str, cid: int, files: list[IDriveFile], note: Note, reply_to_message_id: int ): groups = await get_media_group(files) if len(groups) == 0: - return await send_text(cid, note, reply_to_message_id) + return await send_text(host, cid, note, reply_to_message_id) photo, video, audio, document, msg = [], [], [], [], None for i in groups: if isinstance(i, InputMediaPhoto): @@ -278,10 +284,10 @@ async def send_group( ) if msg and isinstance(msg, list): msg = msg[0] - await send_text(cid, note, msg.id if msg else None) + await send_text(host, cid, note, msg.id if msg else None) -async def send_update(cid: int, note: Note, topic_id: int): +async def send_update(host: str, cid: int, note: Note, topic_id: int): files = list(note.files) if note.reply: files.extend(iter(note.reply.files)) @@ -290,18 +296,18 @@ async def send_update(cid: int, note: Note, topic_id: int): files = list({f.get("id"): f for f in files}.values()) match len(files): case 0: - await send_text(cid, note, topic_id) + await send_text(host, cid, note, topic_id) case 1: file = files[0] file_url = file.get("url", None) file_type = file.get("type", "") if file_type.startswith("image"): - await send_photo(cid, file_url, note, topic_id) + await send_photo(host, cid, file_url, note, topic_id) elif file_type.startswith("video"): - await send_video(cid, file_url, note, topic_id) + await send_video(host, cid, file_url, note, topic_id) elif file_type.startswith("audio"): - await send_audio(cid, file_url, note, topic_id) + await send_audio(host, cid, file_url, note, topic_id) else: - await send_document(cid, file, note, topic_id) + await send_document(host, cid, file, note, topic_id) case _: - await send_group(cid, files, note, topic_id) + await send_group(host, cid, files, note, topic_id) diff --git a/defs/notice.py b/defs/notice.py index 0e56ca0..cb12cd7 100644 --- a/defs/notice.py +++ b/defs/notice.py @@ -1,10 +1,11 @@ from json import load + +from mipac.models.lite.user import LiteUser from mipac.models.notification import ( NotificationFollow, NotificationFollowRequest, NotificationAchievement, ) -from mipac.models.lite.user import LiteUser from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from init import bot diff --git a/defs/search_user.py b/defs/search_user.py index c8482ec..3d20d8a 100644 --- a/defs/search_user.py +++ b/defs/search_user.py @@ -1,6 +1,6 @@ +from datetime import datetime, timedelta from typing import Optional -from datetime import datetime, timedelta from mipac import UserDetailed from mipac.errors import FailedToResolveRemoteUserError from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup diff --git a/gen/achievement.json b/gen/achievement.json index 60468d6..c069481 100644 --- a/gen/achievement.json +++ b/gen/achievement.json @@ -2,157 +2,157 @@ "notes1": [ "初来乍到", "第一次发帖", - "祝您在Misskey玩的愉快~" + "祝您在 Misskey 玩的愉快~" ], "notes10": [ "一些帖子", - "发布了10篇帖子", + "发布了 10 篇帖子", "" ], "notes100": [ "很多帖子", - "发布了100篇帖子", + "发布了 100 篇帖子", "" ], "notes500": [ "满是帖子", - "发布了500篇帖子", + "发布了 500 篇帖子", "" ], "notes1000": [ "积帖成山", - "发布了1,000篇帖子", + "发布了 1,000 篇帖子", "" ], "notes5000": [ "帖如泉涌", - "发布了5,000篇帖子", + "发布了 5,000 篇帖子", "" ], "notes10000": [ "超级帖", - "发布了10,000篇帖子", + "发布了 10,000 篇帖子", "" ], "notes20000": [ "还想要更多帖子", - "发布了20,000篇帖子", + "发布了 20,000 篇帖子", "" ], "notes30000": [ "帖子帖子帖子", - "发布了30,000篇帖子", + "发布了 30,000 篇帖子", "" ], "notes40000": [ "帖子工厂", - "发布了40,000篇帖子", + "发布了 40,000 篇帖子", "" ], "notes50000": [ "帖子星球", - "发布了50,000篇帖子", + "发布了 50,000 篇帖子", "" ], "notes60000": [ "帖子类星体", - "发布了60,000篇帖子", + "发布了 60,000 篇帖子", "" ], "notes70000": [ "帖子黑洞", - "发布了70,000篇帖子", + "发布了 70,000 篇帖子", "" ], "notes80000": [ "帖子星系", - "发布了80,000篇帖子", + "发布了 80,000 篇帖子", "" ], "notes90000": [ "帖子起源", - "发布了90,000篇帖子", + "发布了 90,000 篇帖子", "" ], "notes100000": [ "ALL YOUR NOTE ARE BELONG TO US", - "发布了100,000篇帖子", + "发布了 100,000 篇帖子", "真的有那么多可以写的东西吗?" ], "login3": [ "初学者 I", - "连续登录3天", - "今天开始我就是Misskist!" + "累计登录 3 天", + "今天开始我就是 Misskist!" ], "login7": [ "初学者 II", - "连续登录7天", + "累计登录 7 天", "您开始习惯了吗?" ], "login15": [ "初学者 III", - "连续登录15天", + "累计登录 15 天", "" ], "login30": [ "Misskist Ⅰ", - "连续登录30天", + "累计登录 30 天", "" ], "login60": [ "Misskist Ⅱ", - "连续登录60天", + "累计登录 60 天", "" ], "login100": [ "Misskist Ⅲ", - "总登入100天", - "那个用户,是Misskist喔" + "累计登入 100 天", + "那个用户,是 Misskist 喔" ], "login200": [ "定期联系Ⅰ", - "总登录天数200天", + "累计登录 200 天", "" ], "login300": [ "定期联系Ⅱ", - "总登录天数300天", + "累计登录 300 天", "" ], "login400": [ "定期联系Ⅲ", - "总登录天数400天", + "累计登录 400 天", "" ], "login500": [ "老熟人Ⅰ", - "总登录天数500天", + "累计登录 500 天", "诸君,我喜欢贴文" ], "login600": [ "老熟人Ⅱ", - "总登录天数600天", + "累计登录 600 天", "" ], "login700": [ "老熟人Ⅲ", - "总登录天数700天", + "累计登录 700 天", "" ], "login800": [ - "帖子大师Ⅰ", - "总登录天数800天", + "帖子大师 Ⅰ", + "累计登录 800 天", "" ], "login900": [ - "帖子大师Ⅱ", - "总登录天数900天", + "帖子大师 Ⅱ", + "累计登录 900 天", "" ], "login1000": [ - "帖子大师Ⅲ", - "总登录天数1000天", - "感谢您使用Misskey!" + "帖子大师 Ⅲ", + "累计登录 1000 天", + "感谢您使用 Misskey!" ], "noteClipped1": [ "忍不住要收藏到便签", @@ -186,22 +186,22 @@ ], "following10": [ "关注,跟随", - "关注超过10人", + "关注超过 10 人", "" ], "following50": [ "我的朋友很多", - "关注超过50人", + "关注超过 50 人", "" ], "following100": [ - "我的朋友很多", - "关注超过100人", + "胜友如云", + "关注超过 100 人", "" ], "following300": [ "朋友成群", - "关注数超过300", + "关注数超过 300", "" ], "followers1": [ @@ -211,37 +211,37 @@ ], "followers10": [ "关注我吧!", - "拥有超过10名关注者", + "拥有超过 10 名关注者", "" ], "followers50": [ "三五成群", - "拥有超过50名关注者", + "拥有超过 50 名关注者", "" ], "followers100": [ "胜友如云", - "拥有超过100名关注者", + "拥有超过 100 名关注者", "" ], "followers300": [ "排列成行", - "拥有超过300名关注者", + "拥有超过 300 名关注者", "" ], "followers500": [ "信号塔", - "拥有超过500名关注者", + "拥有超过 500 名关注者", "" ], "followers1000": [ "大影响家", - "拥有超过1000名关注者", + "拥有超过 1000 名关注者", "" ], "collectAchievements30": [ "成就收藏家", - "获得超过30个成就", + "获得超过 30 个成就", "" ], "viewAchievements3min": [ @@ -251,7 +251,7 @@ ], "iLoveMisskey": [ "I Love Misskey", - "发布\"I ❤ #Misskey\"帖子", + "发布 \"I ❤ #Misskey\" 帖子", "感谢您使用 Misskey ! by 开发团队" ], "foundTreasure": [ @@ -261,12 +261,12 @@ ], "client30min": [ "休息一下!", - "启动客户端超过30分钟", + "启动客户端超过 30 分钟", "" ], "client60min": [ - "Misskey重度依赖", - "启动客户端超过60分钟", + "Misskey 重度依赖", + "启动客户端超过 60 分钟", "" ], "noteDeletedWithin1min": [ @@ -281,7 +281,7 @@ ], "postedAt0min0sec": [ "报时", - "在0点发布一篇帖子", + "在 0 点发布一篇帖子", "嘣 嘣 嘣 Biu——!" ], "selfQuote": [ @@ -291,7 +291,7 @@ ], "htl20npm": [ "流动的时间线", - "在首页时间线的流速超过20npm", + "在首页时间线的流速超过 20npm", "" ], "viewInstanceChart": [ @@ -301,7 +301,7 @@ ], "outputHelloWorldOnScratchpad": [ "Hello, world!", - "在AiScript控制台中输出 hello world", + "在 AiScript 控制台中输出 hello world", "" ], "open3windows": [ @@ -316,7 +316,7 @@ ], "reactWithoutRead": [ "有好好读过吗?", - "在含有100字以上的帖子被发出三秒内做出回应", + "在含有 100 字以上的帖子被发出三秒内做出回应", "" ], "clickedClickHere": [ @@ -326,27 +326,27 @@ ], "justPlainLucky": [ "超高校级的幸运", - "每10秒有0.01的概率自动获得", + "每 10 秒有 0.01 的概率自动获得", "" ], "setNameToSyuilo": [ "像神一样呐", - "将名称设定为syuilo", + "将名称设定为 syuilo", "" ], "passedSinceAccountCreated1": [ "一周年", - "账户创建时间超过1年", + "账户创建时间超过 1 年", "" ], "passedSinceAccountCreated2": [ "二周年", - "账户创建时间超过2年", + "账户创建时间超过 2 年", "" ], "passedSinceAccountCreated3": [ "三周年", - "账户创建时间超过3年", + "账户创建时间超过 3 年", "" ], "loggedInOnBirthday": [ @@ -366,7 +366,7 @@ ], "brainDiver": [ "Brain Diver", - "发布了包含Brain Diver链接的帖子", + "发布了包含 Brain Diver 链接的帖子", "Misskey-Misskey La-Tu-Ma" ] } \ No newline at end of file diff --git a/gen/achievement.py b/gen/achievement.py index 6d12efb..fbda7a0 100644 --- a/gen/achievement.py +++ b/gen/achievement.py @@ -1,7 +1,8 @@ -import yaml -from pathlib import Path -from httpx import get from json import dump +from pathlib import Path + +import yaml +from httpx import get json_path = Path(__file__).parent / "achievement.json" diff --git a/glover.py b/glover.py index c701ac4..d096991 100644 --- a/glover.py +++ b/glover.py @@ -1,4 +1,3 @@ -import re from configparser import RawConfigParser from typing import Union from distutils.util import strtobool @@ -9,9 +8,6 @@ api_hash: str = "" # [Basic] ipv6: Union[bool, str] = "False" # [misskey] -misskey_url: str = "" -misskey_host: str = "" -misskey_domain: str = "" web_domain: str = "" admin: int = 0 @@ -20,20 +16,9 @@ config.read("config.ini") api_id = config.getint("pyrogram", "api_id", fallback=api_id) api_hash = config.get("pyrogram", "api_hash", fallback=api_hash) ipv6 = config.get("basic", "ipv6", fallback=ipv6) -misskey_url = config.get("misskey", "url", fallback=misskey_url) -if origin_url := re.search(r"wss?://(.*)/streaming", misskey_url): - misskey_host = ( - origin_url[0] - .replace("wss", "https") - .replace("ws", "http") - .replace("/streaming", "") - ) -else: - misskey_host = misskey_url -misskey_domain = re.search(r"https?://(.*)", misskey_host)[1] web_domain = config.get("misskey", "web_domain", fallback=web_domain) admin = config.getint("misskey", "admin", fallback=admin) try: - ipv6 = strtobool(ipv6) + ipv6 = bool(strtobool(ipv6)) except ValueError: ipv6 = False diff --git a/misskey_init.py b/misskey_init.py index 6152a46..f182a71 100644 --- a/misskey_init.py +++ b/misskey_init.py @@ -23,7 +23,6 @@ from defs.notice import ( send_follow_request_accept, send_achievement_earned, ) -from glover import misskey_url, misskey_host from models.models.user import User, TokenStatusEnum from models.services.user import UserAction @@ -45,41 +44,37 @@ class MisskeyBot(commands.Bot): await Router(ws).connect_channel(["main", "home"]) async def on_note(self, note: Note): - if self.tg_user: - await send_update(self.tg_user.chat_id, note, self.tg_user.timeline_topic) + await send_update( + self.tg_user.host, self.tg_user.chat_id, note, self.tg_user.timeline_topic + ) async def on_user_followed(self, notice: NotificationFollow): - if self.tg_user: - await send_user_followed( - self.tg_user.chat_id, notice, self.tg_user.notice_topic - ) + await send_user_followed( + self.tg_user.chat_id, notice, self.tg_user.notice_topic + ) async def on_follow_request(self, notice: NotificationFollowRequest): - if self.tg_user: - await send_follow_request( - self.tg_user.chat_id, notice, self.tg_user.notice_topic - ) + await send_follow_request( + self.tg_user.chat_id, notice, self.tg_user.notice_topic + ) async def on_follow_request_accept(self, notice: NotificationFollowRequest): - if self.tg_user: - await send_follow_request_accept( - self.tg_user.chat_id, notice, self.tg_user.notice_topic - ) + await send_follow_request_accept( + self.tg_user.chat_id, notice, self.tg_user.notice_topic + ) async def on_chat(self, message: ChatMessage): - if self.tg_user: - await send_chat_message( - self.tg_user.chat_id, message, self.tg_user.notice_topic - ) + await send_chat_message( + self.tg_user.host, self.tg_user.chat_id, message, self.tg_user.notice_topic + ) async def on_chat_unread_message(self, message: ChatMessage): await message.api.read() async def on_achievement_earned(self, notice: NotificationAchievement): - if self.tg_user: - await send_achievement_earned( - self.tg_user.chat_id, notice, self.tg_user.notice_topic - ) + await send_achievement_earned( + self.tg_user.chat_id, notice, self.tg_user.notice_topic + ) misskey_bot_map: dict[int, MisskeyBot] = {} @@ -99,16 +94,16 @@ async def run(user: User): misskey = await create_or_get_misskey_bot(user) try: logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}") - await misskey.start(misskey_url, user.token) + await misskey.start(f"wss://{user.host}", user.token) except ClientConnectorError: await sleep(3) await run(user) -async def test_token(token: str) -> bool: +async def test_token(host: str, token: str) -> bool: try: - logs.info(f"验证 Token {token}") - client = MisskeyClient(misskey_host, token) + logs.info(f"验证 Token {host} {token}") + client = MisskeyClient(f"https://{host}", token) await client.http.login() await client.http.close_session() return True @@ -124,7 +119,7 @@ async def rerun_misskey_bot(user_id: int) -> bool: user = await UserAction.get_user_if_ok(user_id) if not user: return False - if not await test_token(user.token): + if not await test_token(user.host, user.token): await UserAction.set_user_status(user_id, TokenStatusEnum.INVALID_TOKEN) return False bot.loop.create_task(run(user)) @@ -135,7 +130,7 @@ async def init_misskey_bot(): await sqlite.create_db_and_tables() count = 0 for user in await UserAction.get_all_token_ok_users(): - if not await test_token(user.token): + if not await test_token(user.host, user.token): user.status = TokenStatusEnum.INVALID_TOKEN await UserAction.update_user(user) continue diff --git a/models/models/user.py b/models/models/user.py index 4cdd2d5..8a07c9e 100644 --- a/models/models/user.py +++ b/models/models/user.py @@ -12,8 +12,9 @@ class User(SQLModel, table=True): __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci") user_id: int = Field(primary_key=True) + host: str = Field(default="") token: str = Field(default="") status: TokenStatusEnum = Field(sa_column=Column(Enum(TokenStatusEnum))) - chat_id: int = Field(default=0) + chat_id: int = Field(default=0, primary_key=True) timeline_topic: int = Field(default=0) notice_topic: int = Field(default=0) diff --git a/models/services/user.py b/models/services/user.py index 6bd3a9f..a73efce 100644 --- a/models/services/user.py +++ b/models/services/user.py @@ -35,6 +35,7 @@ class UserAction: .where(User.timeline_topic != 0) .where(User.notice_topic != 0) .where(User.token != "") + .where(User.host != "") ) results = await session.exec(statement) return user[0] if (user := results.first()) else None @@ -80,12 +81,22 @@ class UserAction: return True @staticmethod - async def change_user_token(user_id: int, token: str) -> bool: + async def change_user_host(user_id: int, host: str) -> bool: user = await UserAction.get_user_by_id(user_id) if not user: user = User( - user_id=user_id, token=token, status=TokenStatusEnum.STATUS_SUCCESS + user_id=user_id, host=host, status=TokenStatusEnum.INVALID_TOKEN ) + user.host = host + user.status = TokenStatusEnum.INVALID_TOKEN + await UserAction.update_user(user) + return True + + @staticmethod + async def change_user_token(user_id: int, token: str) -> bool: + user = await UserAction.get_user_by_id(user_id) + if not user: + return False user.token = token user.status = TokenStatusEnum.STATUS_SUCCESS await UserAction.update_user(user) diff --git a/models/sqlite.py b/models/sqlite.py index 6b05f9f..a17791d 100644 --- a/models/sqlite.py +++ b/models/sqlite.py @@ -1,7 +1,8 @@ +from pathlib import Path + from sqlmodel import SQLModel from models.models.user import User -from pathlib import Path __all__ = ["User", "Sqlite"] diff --git a/modules/announcement.py b/modules/announcement.py index 1d82db8..1c7119e 100644 --- a/modules/announcement.py +++ b/modules/announcement.py @@ -1,7 +1,6 @@ -from models.services.scheduler import scheduler from defs.announcement import get_unread_announcements - from misskey_init import misskey_bot_map +from models.services.scheduler import scheduler @scheduler.scheduled_job("interval", minutes=15, id="check_announcement") diff --git a/modules/bind.py b/modules/bind.py index 4c43ed7..eb1a4d4 100644 --- a/modules/bind.py +++ b/modules/bind.py @@ -1,9 +1,8 @@ from pyrogram import Client, filters from pyrogram.types import Message -from models.services.user import UserAction - from misskey_init import rerun_misskey_bot +from models.services.user import UserAction async def pre_check(message: Message): diff --git a/modules/search_user.py b/modules/search_user.py index 24f82b8..6b81cdc 100644 --- a/modules/search_user.py +++ b/modules/search_user.py @@ -1,4 +1,5 @@ import contextlib + from mipac.errors import ( InternalErrorError, AlreadyFollowingError, diff --git a/modules/start.py b/modules/start.py index b2f7226..d6cf38b 100644 --- a/modules/start.py +++ b/modules/start.py @@ -1,22 +1,25 @@ from pyrogram import Client, filters from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton -from glover import misskey_host, web_domain, misskey_domain +from defs.check_node import get_host, check_host +from glover import web_domain from init import bot from misskey_init import test_token, rerun_misskey_bot from models.services.user import UserAction des = f"""欢迎使用 {bot.me.first_name},这是一个用于在 Telegram 上使用 Misskey 的机器人。按下方教程开始使用: -1. 点击下方按钮绑定 Misskey 账号 +1. 使用 `/start https://[misskey_domain]` 设置账号所在 Misskey 实例地址(仅支持 https 链接) -2. 在论坛群组中使用 /bind_timeline 绑定 Timeline 话题,接收时间线更新 +2. 点击 start 之后回复你的按钮绑定所在 Misskey 实例的账号 -3. 在论坛群组中使用 /bind_notice 绑定 Notice 话题,接收通知 +3. 在论坛群组中使用 /bind_timeline 绑定 Timeline 话题,接收时间线更新 + +4. 在论坛群组中使用 /bind_notice 绑定 Notice 话题,接收通知 至此,你便可以在 Telegram 接收 Misskey 消息,同时你可以私聊我使用 /status 查看 Bot 运行状态 -此 Bot 仅支持绑定 {misskey_host} 的 Misskey 账号!""" +此 Bot 仅支持 Misskey V13 实例的账号!""" async def finish_check(message: Message): @@ -26,8 +29,40 @@ async def finish_check(message: Message): await message.reply("Token 设置完成,请绑定群组。", quote=True) -def gen_url(): - return f"https://{web_domain}/gen?host={misskey_domain}&back_host={web_domain}&username={bot.me.username}" +def gen_url(domain: str): + return f"https://{web_domain}/gen?host={domain}&back_host={web_domain}&username={bot.me.username}" + + +async def change_host(message: Message, token_or_host: str): + host = get_host(token_or_host) + if await check_host(host): + await UserAction.change_user_host(message.from_user.id, host) + await message.reply( + "Host 验证成功,请点击下方按钮绑定账号。", + quote=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="绑定 Misskey 账号", url=gen_url(host)), + ] + ] + ), + ) + else: + await message.reply("Host 验证失败,请检查 Host 是否正在运行 Misskey V13", quote=True) + + +async def change_token(message: Message, token_or_host: str): + if user := await UserAction.get_user_by_id(message.from_user.id): + if user.host: + if await test_token(user.host, token_or_host): + await UserAction.change_user_token(message.from_user.id, token_or_host) + await message.reply( + "Token 验证成功,绑定账号完成。\n当你撤销此登录时,你可以重新点击按钮授权。", quote=True + ) + await finish_check(message) + else: + await message.reply("Token 验证失败,请检查 Token 是否正确", quote=True) @Client.on_message(filters.incoming & filters.private & filters.command(["start"])) @@ -36,27 +71,13 @@ async def start_command(_: Client, message: Message): 回应 start """ if len(message.command) == 2: - token = message.command[1] - if not token: + token_or_host = message.command[1] + if not token_or_host: await message.reply(des, quote=True) return - if await test_token(token): - await UserAction.change_user_token(message.from_user.id, token) - await message.reply( - "Token 验证成功,绑定账号完成。\n" "当你撤销此登录时,你可以重新点击按钮授权。", quote=True - ) - await finish_check(message) - else: - await message.reply("Token 验证失败,请检查 Token 是否正确", quote=True) + if token_or_host.startswith("https://"): + await change_host(message, token_or_host) + return + await change_token(message, token_or_host) return - await message.reply( - des, - quote=True, - reply_markup=InlineKeyboardMarkup( - [ - [ - InlineKeyboardButton(text="绑定 Misskey 账号", url=gen_url()), - ] - ] - ), - ) + await message.reply(des, quote=True) diff --git a/modules/status.py b/modules/status.py index ce6bf75..7749408 100644 --- a/modules/status.py +++ b/modules/status.py @@ -2,7 +2,6 @@ from pyrogram import filters, Client from pyrogram.types import Message from glover import admin - from misskey_init import get_misskey_bot, rerun_misskey_bot, misskey_bot_map from models.models.user import TokenStatusEnum from models.services.user import UserAction diff --git a/requirements.txt b/requirements.txt index 2a1fe3a..2b04b82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ apscheduler==3.10.1 sqlalchemy==1.4.41 sqlmodel==0.0.8 aiosqlite==0.19.0 -PyYAML==6.0 +PyYAML==6.0.1