diff --git a/defs/chat.py b/defs/chat.py new file mode 100644 index 0000000..168d2f2 --- /dev/null +++ b/defs/chat.py @@ -0,0 +1,151 @@ +from typing import Optional + +from mipac import ChatMessage, File +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 scheduler import add_delete_file_job, delete_file + + +def get_user_link(user: LiteUser) -> str: + if user.host: + return f"https://{user.host}/@{user.username}" + return f"https://{misskey_host}/@{user.username}" + + +def get_source_link(message: ChatMessage) -> str: + return ( + f"https://{misskey_host}/my/messaging/{message.user.username}?cid={message.user.id}" + if not message.group and message.user + else f"https://{misskey_host}/my/messaging/group/{message.group.id}" + ) + + +def gen_button(message: ChatMessage): + author = get_user_link(message.user) + source = get_source_link(message) + first_line = [ + InlineKeyboardButton(text="Chat", url=source), + InlineKeyboardButton(text="Author", url=author), + ] + return InlineKeyboardMarkup([first_line]) + + +def get_content(message: ChatMessage) -> str: + content = message.text or "" + content = content[:768] + user = f"{message.user.nickname}" + if message.group: + 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): + await bot.send_message( + cid, + get_content(message), + reply_to_message_id=reply_to_message_id, + reply_markup=gen_button(message), + disable_web_page_preview=True, + ) + + +def deprecated_to_text(func): + async def wrapper(*args, **kwargs): + try: + return await func(*args, **kwargs) + except MediaEmpty: + return await send_text(args[0], args[2], args[3]) + + return wrapper + + +@deprecated_to_text +async def send_photo(cid: int, url: str, message: ChatMessage, reply_to_message_id: int): + if not url: + return await send_text(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), + ) + + +@deprecated_to_text +async def send_video(cid: int, url: str, message: ChatMessage, reply_to_message_id: int): + if not url: + return await send_text(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), + ) + + +@deprecated_to_text +async def send_audio(cid: int, url: str, message: ChatMessage, reply_to_message_id: int): + if not url: + return await send_text(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), + ) + + +async def fetch_document(file: File) -> Optional[str]: + file_name = f"downloads/{file.name}" + file_url = file.url + if file.size > 10 * 1024 * 1024: + return file_url + if not file_url: + return file_url + req = await request.get(file_url) + if req.status_code != 200: + return file_url + with open(file_name, "wb") as f: + f.write(req.content) + add_delete_file_job(file_name) + return file_name + + +@deprecated_to_text +async def send_document(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) + await bot.send_document( + cid, + file, + reply_to_message_id=reply_to_message_id, + caption=get_content(message), + reply_markup=gen_button(message), + ) + await delete_file(file) + + +async def send_chat_message(cid: int, message: ChatMessage, topic_id: int): + if not message.file: + return await send_text(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) + elif file_type.startswith("video"): + await send_video(cid, file_url, message, topic_id) + elif file_type.startswith("audio"): + await send_audio(cid, file_url, message, topic_id) + else: + await send_document(cid, message.file, message, topic_id) diff --git a/defs/confirm.py b/defs/confirm.py index be18823..69309b1 100644 --- a/defs/confirm.py +++ b/defs/confirm.py @@ -41,7 +41,51 @@ class ReadySend: except Exception as e: await msg.edit(f"发送失败:{e}") else: - await msg.edit("发送成功") + await msg.delete() + finally: + del ready_send[msg.id] + + +class ReadySendMessage: + def __init__( + self, + text: str, + group: bool = False, + uid: Optional[str] = None, + file_id: Optional[str] = None, + ): + self.text = text + self.user_id = None if group else uid + self.group_id = uid if group else None + self.file_id = file_id + + async def confirm(self, msg: Message): + msg = await msg.reply( + "确认发送?", + quote=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="发送", callback_data="chat_send"), + InlineKeyboardButton(text="拒绝", callback_data="delete") + ] + ] + ), + ) + ready_send[msg.id] = self + + async def send(self, msg: Message): + try: + await misskey_bot.core.api.chat.action.send( + text=self.text, + user_id=self.user_id, + group_id=self.group_id, + file_id=self.file_id, + ) + except Exception as e: + await msg.edit(f"发送失败:{e}") + else: + await msg.delete() finally: del ready_send[msg.id] diff --git a/defs/misskey.py b/defs/misskey.py index 23cde3d..3a614ae 100644 --- a/defs/misskey.py +++ b/defs/misskey.py @@ -264,7 +264,7 @@ async def send_group(cid: int, files: list[IDriveFile], note: Note, reply_to_mes await send_text(cid, note, msg.id if msg else None) -async def send_update(cid: int, note: Note, topic_id: int = None): +async def send_update(cid: int, note: Note, topic_id: int): files = list(note.files) if note.reply: files.extend(iter(note.reply.files)) diff --git a/misskey_init.py b/misskey_init.py index dae1596..f9b6c64 100644 --- a/misskey_init.py +++ b/misskey_init.py @@ -1,10 +1,11 @@ from mipa.ext import commands from mipa.router import Router -from mipac import Note, NotificationFollow, NotificationFollowRequest +from mipac import Note, NotificationFollow, NotificationFollowRequest, ChatMessage +from defs.chat import send_chat_message from defs.misskey import send_update from defs.notice import send_user_followed, send_follow_request, send_follow_request_accept -from glover import admin, topic_group_id, timeline_topic_id +from glover import admin, topic_group_id, timeline_topic_id, notice_topic_id class MisskeyBot(commands.Bot): @@ -29,5 +30,11 @@ class MisskeyBot(commands.Bot): async def on_follow_request_accept(self, notice: NotificationFollowRequest): await send_follow_request_accept(notice) + async def on_chat(self, message: ChatMessage): + await send_chat_message(topic_group_id or admin, message, notice_topic_id) + + async def on_chat_unread_message(self, message: ChatMessage): + await message.api.read() + misskey_bot = MisskeyBot() diff --git a/modules/chat.py b/modules/chat.py new file mode 100644 index 0000000..453a1db --- /dev/null +++ b/modules/chat.py @@ -0,0 +1,69 @@ +import contextlib +from os import remove +from typing import Tuple + +from pyrogram import Client, filters, ContinuePropagation +from pyrogram.types import Message, CallbackQuery + +from defs.confirm import ready_send, ReadySendMessage +from glover import admin +from init import notice_filter +from misskey_init import misskey_bot + + +def get_uid(message: Message) -> Tuple[bool, str]: + group, user, uid = False, None, None + if ( + not message.reply_to_message + or not message.reply_to_message.reply_markup + ): + raise ContinuePropagation + with contextlib.suppress(IndexError, AttributeError): + url = message.reply_to_message.reply_markup.inline_keyboard[0][0].url + user = url.split("/")[-1] + if "/my/messaging/group/" in url: + group = True + uid = user + else: + uid = user.split("?cid=")[1] + if not user: + raise ContinuePropagation + if not uid: + raise ContinuePropagation + return group, uid + + +@Client.on_message(filters.incoming & notice_filter & filters.text & filters.user(admin)) +async def chat_command(_: Client, message: Message): + group, uid = get_uid(message) + text = message.text.strip() + if text.startswith("@"): + raise ContinuePropagation + need_send = ReadySendMessage(text, group, uid) + await need_send.confirm(message) + + +@Client.on_message(filters.incoming & notice_filter & filters.photo & filters.user(admin)) +async def chat_photo_command(_: Client, message: Message): + group, uid = get_uid(message) + text = message.caption.strip() if message.caption else "" + photo = await message.download() + try: + file_ = await misskey_bot.core.api.drive.file.action.upload_file(photo) + except Exception as e: + return await message.reply(f"上传文件失败:{e}", quote=True) + need_send = ReadySendMessage(text, group, uid, file_.id) + remove(photo) + await need_send.confirm(message) + + +@Client.on_callback_query(filters.regex("^chat_send$")) +async def chat_send_callback(_: Client, callback_query: CallbackQuery): + """ + 发送 + """ + if need_send := ready_send.get(callback_query.message.id, None): + await need_send.send(callback_query.message) + return await callback_query.answer("发送成功") + else: + return await callback_query.answer("按钮已过期", show_alert=True) diff --git a/modules/search_user.py b/modules/search_user.py index ca54776..2eab2ea 100644 --- a/modules/search_user.py +++ b/modules/search_user.py @@ -1,3 +1,4 @@ +import contextlib from mipac.errors import InternalErrorError, AlreadyFollowingError, FolloweeIsYourselfError from pyrogram import Client, filters from pyrogram.types import Message, CallbackQuery @@ -42,6 +43,8 @@ async def follow_user_callback(_: Client, callback_query: CallbackQuery): 关注/取消关注用户 """ user_id = callback_query.matches[0].group(1) + button = callback_query.message.reply_markup + follow = True try: await misskey_bot.core.api.follow.action.add(user_id) await callback_query.answer("关注成功", show_alert=True) @@ -51,12 +54,16 @@ async def follow_user_callback(_: Client, callback_query: CallbackQuery): try: await misskey_bot.core.api.follow.action.remove(user_id) await callback_query.answer("取消关注成功", show_alert=True) + follow = False except Exception as e: await callback_query.answer("取消关注失败", show_alert=True) await callback_query.message.reply(f"取消关注失败:{e}", quote=True) - return except FolloweeIsYourselfError: await callback_query.answer("不能关注自己", show_alert=True) except Exception as e: await callback_query.answer("关注失败", show_alert=True) await callback_query.message.reply(f"关注失败:{e}", quote=True) + if button: + with contextlib.suppress(Exception): + button.inline_keyboard[1][0].text = "➖" if follow else "➕" + await callback_query.message.edit_reply_markup(button) diff --git a/requirements.git.txt b/requirements.git.txt new file mode 100644 index 0000000..e68d1f5 --- /dev/null +++ b/requirements.git.txt @@ -0,0 +1,2 @@ +git+https://github.com/yupix/MiPA.git +git+https://github.com/yupix/MiPAC.git diff --git a/requirements.txt b/requirements.txt index 167b23b..41b1a6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -git+https://github.com/yupix/MiPA.git -git+https://github.com/yupix/MiPAC.git Pyrogram==2.0.97 tgCrypto==1.2.5 httpx==0.23.3