mirror of
https://github.com/Xtao-Labs/misskey2telegram.git
synced 2024-11-22 05:53:09 +00:00
feat: revoke note
This commit is contained in:
parent
0964f9f3f4
commit
5d1d534d19
@ -1,11 +1,10 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
import aiofiles as aiofiles
|
import aiofiles as aiofiles
|
||||||
from mipac import Note
|
from mipac import Note, File
|
||||||
from mipac.models.lite import LiteUser
|
from mipac.models.lite import LiteUser
|
||||||
from mipac.types import IDriveFile
|
|
||||||
from pyrogram.enums import ParseMode
|
from pyrogram.enums import ParseMode
|
||||||
from pyrogram.errors import MediaEmpty
|
from pyrogram.errors import MediaEmpty
|
||||||
from pyrogram.types import (
|
from pyrogram.types import (
|
||||||
@ -15,6 +14,7 @@ from pyrogram.types import (
|
|||||||
InputMediaVideo,
|
InputMediaVideo,
|
||||||
InputMediaDocument,
|
InputMediaDocument,
|
||||||
InputMediaAudio,
|
InputMediaAudio,
|
||||||
|
Message,
|
||||||
)
|
)
|
||||||
|
|
||||||
from defs.image import webp_to_png
|
from defs.image import webp_to_png
|
||||||
@ -106,8 +106,8 @@ def get_content(host: str, note: Note) -> str:
|
|||||||
|
|
||||||
async def send_text(
|
async def send_text(
|
||||||
host: str, cid: int, note: Note, reply_to_message_id: int, show_second: bool
|
host: str, cid: int, note: Note, reply_to_message_id: int, show_second: bool
|
||||||
):
|
) -> Message:
|
||||||
await bot.send_message(
|
return await bot.send_message(
|
||||||
cid,
|
cid,
|
||||||
get_content(host, note),
|
get_content(host, note),
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
@ -128,10 +128,10 @@ def deprecated_to_text(func):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
async def fetch_document(file: IDriveFile) -> Optional[str]:
|
async def fetch_document(file: File) -> Optional[str]:
|
||||||
file_name = "downloads/" + file.get("name", "file")
|
file_name = "downloads/" + file.name
|
||||||
file_url = file.get("url", None)
|
file_url = file.url
|
||||||
if file.get("size", 0) > 10 * 1024 * 1024:
|
if file.size > 10 * 1024 * 1024:
|
||||||
return file_url
|
return file_url
|
||||||
if not file_url:
|
if not file_url:
|
||||||
return file_url
|
return file_url
|
||||||
@ -157,10 +157,10 @@ async def send_photo(
|
|||||||
note: Note,
|
note: Note,
|
||||||
reply_to_message_id: int,
|
reply_to_message_id: int,
|
||||||
show_second: bool,
|
show_second: bool,
|
||||||
):
|
) -> Message:
|
||||||
if not url:
|
if not url:
|
||||||
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
||||||
await bot.send_photo(
|
return await bot.send_photo(
|
||||||
cid,
|
cid,
|
||||||
url,
|
url,
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
@ -179,10 +179,10 @@ async def send_video(
|
|||||||
note: Note,
|
note: Note,
|
||||||
reply_to_message_id: int,
|
reply_to_message_id: int,
|
||||||
show_second: bool,
|
show_second: bool,
|
||||||
):
|
) -> Message:
|
||||||
if not url:
|
if not url:
|
||||||
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
||||||
await bot.send_video(
|
return await bot.send_video(
|
||||||
cid,
|
cid,
|
||||||
url,
|
url,
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
@ -201,10 +201,10 @@ async def send_audio(
|
|||||||
note: Note,
|
note: Note,
|
||||||
reply_to_message_id: int,
|
reply_to_message_id: int,
|
||||||
show_second: bool,
|
show_second: bool,
|
||||||
):
|
) -> Message:
|
||||||
if not url:
|
if not url:
|
||||||
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
||||||
await bot.send_audio(
|
return await bot.send_audio(
|
||||||
cid,
|
cid,
|
||||||
url,
|
url,
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
@ -223,10 +223,10 @@ async def send_document(
|
|||||||
note: Note,
|
note: Note,
|
||||||
reply_to_message_id: int,
|
reply_to_message_id: int,
|
||||||
show_second: bool,
|
show_second: bool,
|
||||||
):
|
) -> Message:
|
||||||
if not url:
|
if not url:
|
||||||
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
||||||
await bot.send_document(
|
msg = await bot.send_document(
|
||||||
cid,
|
cid,
|
||||||
url,
|
url,
|
||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
@ -237,15 +237,16 @@ async def send_document(
|
|||||||
)
|
)
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
await delete_file(url)
|
await delete_file(url)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
async def get_media_group(files: list[IDriveFile]) -> list:
|
async def get_media_group(files: list[File]) -> list:
|
||||||
media_lists = []
|
media_lists = []
|
||||||
for file_ in files:
|
for file_ in files:
|
||||||
file_url = file_.get("url", None)
|
file_url = file_.url
|
||||||
if not file_url:
|
if not file_url:
|
||||||
continue
|
continue
|
||||||
file_type = file_.get("type", "")
|
file_type = file_.type
|
||||||
if file_type.startswith("image"):
|
if file_type.startswith("image"):
|
||||||
media_lists.append(
|
media_lists.append(
|
||||||
InputMediaPhoto(
|
InputMediaPhoto(
|
||||||
@ -280,14 +281,14 @@ async def get_media_group(files: list[IDriveFile]) -> list:
|
|||||||
async def send_group(
|
async def send_group(
|
||||||
host: str,
|
host: str,
|
||||||
cid: int,
|
cid: int,
|
||||||
files: list[IDriveFile],
|
files: list[File],
|
||||||
note: Note,
|
note: Note,
|
||||||
reply_to_message_id: int,
|
reply_to_message_id: int,
|
||||||
show_second: bool,
|
show_second: bool,
|
||||||
):
|
) -> List[Message]:
|
||||||
groups = await get_media_group(files)
|
groups = await get_media_group(files)
|
||||||
if len(groups) == 0:
|
if len(groups) == 0:
|
||||||
return await send_text(host, cid, note, reply_to_message_id, show_second)
|
return [await send_text(host, cid, note, reply_to_message_id, show_second)]
|
||||||
photo, video, audio, document, msg = [], [], [], [], None
|
photo, video, audio, document, msg = [], [], [], [], None
|
||||||
for i in groups:
|
for i in groups:
|
||||||
if isinstance(i, InputMediaPhoto):
|
if isinstance(i, InputMediaPhoto):
|
||||||
@ -341,33 +342,41 @@ async def send_group(
|
|||||||
reply_to_message_id=reply_to_message_id,
|
reply_to_message_id=reply_to_message_id,
|
||||||
)
|
)
|
||||||
if msg and isinstance(msg, list):
|
if msg and isinstance(msg, list):
|
||||||
msg = msg[0]
|
msg_ids = msg
|
||||||
await send_text(host, cid, note, msg.id if msg else None, show_second)
|
elif msg:
|
||||||
|
msg_ids = [msg]
|
||||||
|
else:
|
||||||
|
msg_ids = []
|
||||||
|
tmsg = await send_text(
|
||||||
|
host, cid, note, msg_ids[0].id if msg_ids else None, show_second
|
||||||
|
)
|
||||||
|
if tmsg:
|
||||||
|
msg_ids.append(tmsg)
|
||||||
|
return msg_ids
|
||||||
|
|
||||||
|
|
||||||
async def send_update(
|
async def send_update(
|
||||||
host: str, cid: int, note: Note, topic_id: Optional[int], show_second: bool
|
host: str, cid: int, note: Note, topic_id: Optional[int], show_second: bool
|
||||||
):
|
) -> List[Message]:
|
||||||
files = list(note.files)
|
files = list(note.files)
|
||||||
if note.reply:
|
if note.reply:
|
||||||
files.extend(iter(note.reply.files))
|
files.extend(iter(note.reply.files))
|
||||||
if note.renote:
|
if note.renote:
|
||||||
files.extend(iter(note.renote.files))
|
files.extend(iter(note.renote.files))
|
||||||
files = list({f.get("id"): f for f in files}.values())
|
|
||||||
match len(files):
|
match len(files):
|
||||||
case 0:
|
case 0:
|
||||||
await send_text(host, cid, note, topic_id, show_second)
|
return [await send_text(host, cid, note, topic_id, show_second)]
|
||||||
case 1:
|
case 1:
|
||||||
file = files[0]
|
file = files[0]
|
||||||
file_type = file.get("type", "")
|
file_type = file.type
|
||||||
url = await fetch_document(file)
|
url = await fetch_document(file)
|
||||||
if file_type.startswith("image"):
|
if file_type.startswith("image"):
|
||||||
await send_photo(host, cid, url, note, topic_id, show_second)
|
return await send_photo(host, cid, url, note, topic_id, show_second)
|
||||||
elif file_type.startswith("video"):
|
elif file_type.startswith("video"):
|
||||||
await send_video(host, cid, url, note, topic_id, show_second)
|
return await send_video(host, cid, url, note, topic_id, show_second)
|
||||||
elif file_type.startswith("audio"):
|
elif file_type.startswith("audio"):
|
||||||
await send_audio(host, cid, url, note, topic_id, show_second)
|
return await send_audio(host, cid, url, note, topic_id, show_second)
|
||||||
else:
|
else:
|
||||||
await send_document(host, cid, url, note, topic_id, show_second)
|
return await send_document(host, cid, url, note, topic_id, show_second)
|
||||||
case _:
|
case _:
|
||||||
await send_group(host, cid, files, note, topic_id, show_second)
|
return await send_group(host, cid, files, note, topic_id, show_second)
|
||||||
|
@ -7,6 +7,7 @@ api_id: int = 0
|
|||||||
api_hash: str = ""
|
api_hash: str = ""
|
||||||
# [Basic]
|
# [Basic]
|
||||||
ipv6: Union[bool, str] = "False"
|
ipv6: Union[bool, str] = "False"
|
||||||
|
cache_uri: str = "mem://"
|
||||||
# [misskey]
|
# [misskey]
|
||||||
web_domain: str = ""
|
web_domain: str = ""
|
||||||
admin: int = 0
|
admin: int = 0
|
||||||
@ -16,6 +17,7 @@ config.read("config.ini")
|
|||||||
api_id = config.getint("pyrogram", "api_id", fallback=api_id)
|
api_id = config.getint("pyrogram", "api_id", fallback=api_id)
|
||||||
api_hash = config.get("pyrogram", "api_hash", fallback=api_hash)
|
api_hash = config.get("pyrogram", "api_hash", fallback=api_hash)
|
||||||
ipv6 = config.get("basic", "ipv6", fallback=ipv6)
|
ipv6 = config.get("basic", "ipv6", fallback=ipv6)
|
||||||
|
cache_uri = config.get("basic", "cache_uri", fallback=cache_uri)
|
||||||
web_domain = config.get("misskey", "web_domain", fallback=web_domain)
|
web_domain = config.get("misskey", "web_domain", fallback=web_domain)
|
||||||
admin = config.getint("misskey", "admin", fallback=admin)
|
admin = config.getint("misskey", "admin", fallback=admin)
|
||||||
try:
|
try:
|
||||||
|
12
init.py
12
init.py
@ -1,19 +1,23 @@
|
|||||||
from logging import getLogger, INFO, ERROR, StreamHandler, basicConfig, CRITICAL, Formatter
|
from logging import getLogger, INFO, StreamHandler, basicConfig, CRITICAL, Formatter
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
|
||||||
|
from cashews import cache
|
||||||
|
|
||||||
from models.fix_topic import fix_topic
|
from models.fix_topic import fix_topic
|
||||||
from glover import api_id, api_hash, ipv6
|
from glover import api_id, api_hash, ipv6, cache_uri
|
||||||
from models.services.scheduler import scheduler
|
from models.services.scheduler import scheduler
|
||||||
from models.sqlite import Sqlite
|
from models.sqlite import Sqlite
|
||||||
|
|
||||||
|
# Set Cache
|
||||||
|
cache.setup(cache_uri)
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logs = getLogger(__name__)
|
logs = getLogger(__name__)
|
||||||
logging_handler = StreamHandler()
|
logging_handler = StreamHandler()
|
||||||
dt_fmt = '%Y-%m-%d %H:%M:%S'
|
dt_fmt = "%Y-%m-%d %H:%M:%S"
|
||||||
formatter = Formatter(
|
formatter = Formatter(
|
||||||
'[{asctime}] [{levelname:<8}] {name}: {message}', dt_fmt, style='{'
|
"[{asctime}] [{levelname:<8}] {name}: {message}", dt_fmt, style="{"
|
||||||
)
|
)
|
||||||
logging_handler.setFormatter(formatter)
|
logging_handler.setFormatter(formatter)
|
||||||
root_logger = getLogger()
|
root_logger = getLogger()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
from asyncio import sleep
|
from asyncio import sleep, Lock
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from aiohttp import ClientConnectorError
|
from aiohttp import ClientConnectorError
|
||||||
@ -12,6 +12,7 @@ from mipac import (
|
|||||||
NotificationFollowRequest,
|
NotificationFollowRequest,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
NotificationAchievement,
|
NotificationAchievement,
|
||||||
|
NoteDeleted,
|
||||||
)
|
)
|
||||||
from mipac.client import Client as MisskeyClient
|
from mipac.client import Client as MisskeyClient
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ from defs.notice import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from models.models.user import User, TokenStatusEnum
|
from models.models.user import User, TokenStatusEnum
|
||||||
|
from models.services.revoke import RevokeAction
|
||||||
from models.services.user import UserAction
|
from models.services.user import UserAction
|
||||||
|
|
||||||
from init import bot, logs, sqlite
|
from init import bot, logs, sqlite
|
||||||
@ -36,30 +38,47 @@ class MisskeyBot(commands.Bot):
|
|||||||
self.user_id: int = user.user_id
|
self.user_id: int = user.user_id
|
||||||
self.instance_user_id: str = user.instance_user_id
|
self.instance_user_id: str = user.instance_user_id
|
||||||
self.tg_user: User = user
|
self.tg_user: User = user
|
||||||
|
self.lock = Lock()
|
||||||
|
|
||||||
|
async def when_start(self, ws):
|
||||||
|
await Router(ws).connect_channel(["main", "home"])
|
||||||
|
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):
|
async def on_ready(self, ws):
|
||||||
await Router(ws).connect_channel(["main", "home"])
|
await self.when_start(ws)
|
||||||
logs.info(f"成功启动 Misskey Bot WS 任务 {self.user_id}")
|
logs.info(f"成功启动 Misskey Bot WS 任务 {self.user_id}")
|
||||||
|
|
||||||
async def on_reconnect(self, ws):
|
async def on_reconnect(self, ws):
|
||||||
await Router(ws).connect_channel(["main", "home"])
|
await self.when_start(ws)
|
||||||
|
logs.info(f"成功重连 Misskey Bot WS 任务 {self.user_id}")
|
||||||
|
|
||||||
async def on_note(self, note: Note):
|
async def on_note(self, note: Note):
|
||||||
logs.info(f"{self.tg_user.user_id} 收到新 note {note.id}")
|
logs.info(f"{self.tg_user.user_id} 收到新 note {note.id}")
|
||||||
if self.tg_user.chat_id != 0 and self.tg_user.timeline_topic != 0:
|
async with self.lock:
|
||||||
await send_update(
|
if self.tg_user.chat_id != 0 and self.tg_user.timeline_topic != 0:
|
||||||
self.tg_user.host,
|
msgs = await send_update(
|
||||||
self.tg_user.chat_id,
|
self.tg_user.host,
|
||||||
note,
|
self.tg_user.chat_id,
|
||||||
self.tg_user.timeline_topic,
|
note,
|
||||||
True,
|
self.tg_user.timeline_topic,
|
||||||
)
|
True,
|
||||||
if note.user_id == self.instance_user_id and self.tg_user.push_chat_id != 0:
|
)
|
||||||
await send_update(
|
await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
|
||||||
self.tg_user.host, self.tg_user.push_chat_id, note, None, False
|
if note.user_id == self.instance_user_id and self.tg_user.push_chat_id != 0:
|
||||||
)
|
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)
|
||||||
logs.info(f"{self.tg_user.user_id} 处理 note {note.id} 完成")
|
logs.info(f"{self.tg_user.user_id} 处理 note {note.id} 完成")
|
||||||
|
|
||||||
|
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} 完成")
|
||||||
|
|
||||||
async def on_user_followed(self, notice: NotificationFollow):
|
async def on_user_followed(self, notice: NotificationFollow):
|
||||||
if self.tg_user.chat_id == 0 or self.tg_user.notice_topic == 0:
|
if self.tg_user.chat_id == 0 or self.tg_user.notice_topic == 0:
|
||||||
return
|
return
|
||||||
|
58
models/services/revoke.py
Normal file
58
models/services/revoke.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import base64
|
||||||
|
from cashews import cache
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from init import bot
|
||||||
|
|
||||||
|
|
||||||
|
class RevokeAction:
|
||||||
|
HOURS: int = 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_messages(messages: list[Message]) -> str:
|
||||||
|
ids = [str(message.id) for message in messages]
|
||||||
|
cid = messages[0].chat.id
|
||||||
|
text = f"{cid}:{','.join(ids)}"
|
||||||
|
return base64.b64encode(text.encode()).decode()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_messages(text: str) -> tuple[int, list[int]]:
|
||||||
|
text = base64.b64decode(text.encode()).decode()
|
||||||
|
cid, ids = text.split(":")
|
||||||
|
return int(cid), [int(mid) for mid in ids.split(",")]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def push(uid: int, note_id: str, messages: list[Message]):
|
||||||
|
await cache.set(
|
||||||
|
f"sub:{uid}:{note_id}",
|
||||||
|
RevokeAction.encode_messages(messages),
|
||||||
|
expire=60 * 60 * RevokeAction.HOURS,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get(uid: int, note_id: str) -> tuple[int, list[int]]:
|
||||||
|
text = await cache.get(f"sub:{uid}:{note_id}")
|
||||||
|
if text is None:
|
||||||
|
raise ValueError("No such sub note: {}".format(note_id))
|
||||||
|
return RevokeAction.decode_messages(text)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_all_subs(uid: int) -> list[str]:
|
||||||
|
keys = []
|
||||||
|
async for key in cache.scan(f"sub:{uid}:*"):
|
||||||
|
key: str
|
||||||
|
keys.append(key[4:])
|
||||||
|
return keys
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def process_delete_note(uid: int, note_id: str):
|
||||||
|
try:
|
||||||
|
cid, msgs = await RevokeAction.get(uid, note_id)
|
||||||
|
except ValueError:
|
||||||
|
return
|
||||||
|
await RevokeAction._delete_message(cid, msgs)
|
||||||
|
await cache.delete(f"sub:{uid}:{note_id}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _delete_message(cid: int, msgs: list[int]):
|
||||||
|
await bot.delete_messages(cid, msgs)
|
@ -15,6 +15,9 @@ help_msg = f"""这里是 Bot 帮助
|
|||||||
|
|
||||||
6. 你可以在 Notice Topic 中发送 @username@hostname 或者 @username 来查找用户,对用户进行关注、取消关注操作
|
6. 你可以在 Notice Topic 中发送 @username@hostname 或者 @username 来查找用户,对用户进行关注、取消关注操作
|
||||||
|
|
||||||
|
7. 请注意:BOT 会持续监听帖子 2 小时,如果 2 小时内删帖则会同步删除 Telegram 推送,使用
|
||||||
|
|
||||||
|
|
||||||
更多功能正在开发中,敬请期待!"""
|
更多功能正在开发中,敬请期待!"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,4 +7,5 @@ sqlmodel==0.0.8
|
|||||||
aiosqlite==0.19.0
|
aiosqlite==0.19.0
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
aiofiles==23.1.0
|
aiofiles==23.1.0
|
||||||
pillow
|
pillow==10.0.0
|
||||||
|
cashews==6.2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user