feat: support spoiler

This commit is contained in:
xtaodada 2023-08-09 15:10:54 +08:00
parent efbe97cd37
commit a02022d3a3
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
8 changed files with 216 additions and 11 deletions

View File

@ -0,0 +1,36 @@
"""config
Revision ID: 3cbe5fbdb7e3
Revises: fcdaa7ac5975
Create Date: 2023-08-09 14:36:48.093192
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "3cbe5fbdb7e3"
down_revision = "fcdaa7ac5975"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"user_config",
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("timeline_spoiler", sa.Boolean(), nullable=False),
sa.Column("push_spoiler", sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint("user_id"),
mysql_charset="utf8mb4",
mysql_collate="utf8mb4_general_ci",
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("user_config")
# ### end Alembic commands ###

View File

@ -202,6 +202,7 @@ async def send_photo(
note: Note, note: Note,
reply_to_message_id: int, reply_to_message_id: int,
show_second: bool, show_second: bool,
spoiler: bool,
) -> Message: ) -> 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)
@ -213,6 +214,7 @@ async def send_photo(
reply_markup=gen_button( reply_markup=gen_button(
host, note, get_user_link(host, note.author), show_second host, note, get_user_link(host, note.author), show_second
), ),
has_spoiler=spoiler,
) )
@ -225,6 +227,7 @@ async def send_gif(
note: Note, note: Note,
reply_to_message_id: int, reply_to_message_id: int,
show_second: bool, show_second: bool,
spoiler: bool,
) -> Message: ) -> 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)
@ -236,6 +239,7 @@ async def send_gif(
reply_markup=gen_button( reply_markup=gen_button(
host, note, get_user_link(host, note.author), show_second host, note, get_user_link(host, note.author), show_second
), ),
has_spoiler=spoiler,
) )
@ -248,6 +252,7 @@ async def send_video(
note: Note, note: Note,
reply_to_message_id: int, reply_to_message_id: int,
show_second: bool, show_second: bool,
spoiler: bool,
) -> Message: ) -> 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)
@ -259,6 +264,7 @@ async def send_video(
reply_markup=gen_button( reply_markup=gen_button(
host, note, get_user_link(host, note.author), show_second host, note, get_user_link(host, note.author), show_second
), ),
has_spoiler=spoiler,
) )
@ -311,7 +317,7 @@ async def send_document(
return msg return msg
async def get_media_group(host: str, files: list[File]) -> list: async def get_media_group(host: str, files: list[File], spoiler: bool) -> list:
media_lists = [] media_lists = []
for file_ in files: for file_ in files:
file_url = await fetch_document(host, file_) file_url = await fetch_document(host, file_)
@ -324,6 +330,7 @@ async def get_media_group(host: str, files: list[File]) -> list:
InputMediaAnimation( InputMediaAnimation(
file_url, file_url,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=file_.is_sensitive and spoiler,
) )
) )
else: else:
@ -331,6 +338,7 @@ async def get_media_group(host: str, files: list[File]) -> list:
InputMediaPhoto( InputMediaPhoto(
file_url, file_url,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=file_.is_sensitive and spoiler,
) )
) )
elif file_type.startswith("video"): elif file_type.startswith("video"):
@ -338,6 +346,7 @@ async def get_media_group(host: str, files: list[File]) -> list:
InputMediaVideo( InputMediaVideo(
file_url, file_url,
parse_mode=ParseMode.HTML, parse_mode=ParseMode.HTML,
has_spoiler=file_.is_sensitive and spoiler,
) )
) )
elif file_type.startswith("audio"): elif file_type.startswith("audio"):
@ -380,8 +389,9 @@ async def send_group(
note: Note, note: Note,
reply_to_message_id: int, reply_to_message_id: int,
show_second: bool, show_second: bool,
spoiler: bool,
) -> List[Message]: ) -> List[Message]:
groups = await get_media_group(host, files) groups = await get_media_group(host, files, spoiler)
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_ids = [], [], [], [], [] photo, video, audio, document, msg_ids = [], [], [], [], []
@ -405,7 +415,12 @@ async def send_group(
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,
spoiler: bool,
) -> Message | list[Message]: ) -> Message | list[Message]:
files = list(note.files) files = list(note.files)
if note.reply: if note.reply:
@ -421,13 +436,21 @@ async def send_update(
url = await fetch_document(host, file) url = await fetch_document(host, file)
if file_type.startswith("image"): if file_type.startswith("image"):
if "gif" in file_type: if "gif" in file_type:
return await send_gif(host, cid, url, note, topic_id, show_second) return await send_gif(
return await send_photo(host, cid, url, note, topic_id, show_second) host, cid, url, note, topic_id, show_second, spoiler
)
return await send_photo(
host, cid, url, note, topic_id, show_second, spoiler
)
elif file_type.startswith("video"): elif file_type.startswith("video"):
return await send_video(host, cid, url, note, topic_id, show_second) return await send_video(
host, cid, url, note, topic_id, show_second, spoiler
)
elif file_type.startswith("audio"): elif file_type.startswith("audio"):
return 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:
return 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 _:
return await send_group(host, cid, files, note, topic_id, show_second) return await send_group(
host, cid, files, note, topic_id, show_second, spoiler
)

26
defs/web_app.py Normal file
View File

@ -0,0 +1,26 @@
from pydantic import BaseModel
from pyrogram import filters
from pyrogram.types import Message
class WebAppUserConfig(BaseModel):
timeline_spoiler: bool
push_spoiler: bool
class WebAppData(BaseModel):
path: str
data: dict
code: int
message: str
@property
def user_config(self) -> WebAppUserConfig:
return WebAppUserConfig(**self.data)
async def web_data_filter(_, __, m: Message):
return bool(m.web_app_data)
filter_web_data = filters.create(web_data_filter)

View File

@ -41,23 +41,27 @@ from defs.notice import (
) )
from models.models.user import User, TokenStatusEnum from models.models.user import User, TokenStatusEnum
from models.models.user_config import UserConfig
from models.services.no_repeat_renote import NoRepeatRenoteAction from models.services.no_repeat_renote import NoRepeatRenoteAction
from models.services.revoke import RevokeAction 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
from models.services.user_config import UserConfigAction
class MisskeyBot(commands.Bot): class MisskeyBot(commands.Bot):
def __init__(self, user: User): def __init__(self, user: User, user_config: UserConfig):
super().__init__() super().__init__()
self._BotBase__on_error = self.__on_error self._BotBase__on_error = self.__on_error
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.user_config: UserConfig = user_config
self.lock = Lock() self.lock = Lock()
async def fetch_offline_notes(self): async def fetch_offline_notes(self):
return
logs.info(f"{self.tg_user.user_id} 开始获取最近十条时间线") logs.info(f"{self.tg_user.user_id} 开始获取最近十条时间线")
data = {"withReplies": False, "limit": 10} data = {"withReplies": False, "limit": 10}
data = await self.core.http.request( data = await self.core.http.request(
@ -110,6 +114,8 @@ class MisskeyBot(commands.Bot):
note, note,
self.tg_user.timeline_topic, self.tg_user.timeline_topic,
True, True,
spoiler=self.user_config
and self.user_config.timeline_spoiler,
) )
await RevokeAction.push(self.tg_user.user_id, note.id, msgs) await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
if self.check_push(note): if self.check_push(note):
@ -119,6 +125,7 @@ class MisskeyBot(commands.Bot):
note, note,
None, None,
False, False,
spoiler=self.user_config and self.user_config.push_spoiler,
) )
await RevokeAction.push(self.tg_user.user_id, note.id, msgs) await RevokeAction.push(self.tg_user.user_id, note.id, msgs)
elif notice: elif notice:
@ -217,14 +224,15 @@ 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] return None if user_id not in misskey_bot_map else misskey_bot_map[user_id]
async def create_or_get_misskey_bot(user: User) -> MisskeyBot: async def create_or_get_misskey_bot(user: User, user_config: UserConfig) -> MisskeyBot:
if user.user_id not in misskey_bot_map: if user.user_id not in misskey_bot_map:
misskey_bot_map[user.user_id] = MisskeyBot(user) misskey_bot_map[user.user_id] = MisskeyBot(user, user_config)
return misskey_bot_map[user.user_id] return misskey_bot_map[user.user_id]
async def run(user: User): async def run(user: User):
misskey = await create_or_get_misskey_bot(user) user_config = await UserConfigAction.get_user_config_by_id(user.user_id)
misskey = await create_or_get_misskey_bot(user, user_config)
try: try:
logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}") logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}")
await misskey.start(f"wss://{user.host}/streaming", user.token, log_level=None) await misskey.start(f"wss://{user.host}/streaming", user.token, log_level=None)

View File

@ -0,0 +1,11 @@
import sqlalchemy as sa
from sqlmodel import SQLModel, Field, Column
class UserConfig(SQLModel, table=True):
__tablename__ = "user_config"
__table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
user_id: int = Field(sa_column=Column(sa.BigInteger, primary_key=True))
timeline_spoiler: bool = Field(default=False)
push_spoiler: bool = Field(default=False)

View File

@ -0,0 +1,36 @@
from typing import cast, Optional
from sqlalchemy import select
from sqlmodel.ext.asyncio.session import AsyncSession
from init import sqlite
from models.models.user_config import UserConfig
class UserConfigAction:
@staticmethod
async def add_user_config(user_config: UserConfig):
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(user_config)
await session.commit()
@staticmethod
async def get_user_config_by_id(user_id: int) -> Optional[UserConfig]:
async with sqlite.session() as session:
session = cast(AsyncSession, session)
statement = select(UserConfig).where(UserConfig.user_id == user_id)
results = await session.exec(statement)
return user[0] if (user := results.first()) else None
@staticmethod
async def update_user_config(user_config: UserConfig):
async with sqlite.session() as session:
session = cast(AsyncSession, session)
session.add(user_config)
await session.commit()
await session.refresh(user_config)
@staticmethod
def create_user_config(user_id: int) -> UserConfig:
return UserConfig(user_id=user_id)

View File

@ -19,6 +19,8 @@ des = f"""欢迎使用 {bot.me.first_name},这是一个用于在 Telegram 上
5. [可选] 在私聊中使用 `/bind_push [对话id]` 绑定本人发帖时推送 /unbind_push 解除绑定 5. [可选] 在私聊中使用 `/bind_push [对话id]` 绑定本人发帖时推送 /unbind_push 解除绑定
6. [可选] 在私聊中使用 `/config` 设置敏感媒体是否自动设置 Spoiler
至此你便可以在 Telegram 接收 Misskey 消息同时你可以私聊我使用 /status 查看 Bot 运行状态 至此你便可以在 Telegram 接收 Misskey 消息同时你可以私聊我使用 /status 查看 Bot 运行状态
Bot 仅支持 Misskey V13 实例的账号""" Bot 仅支持 Misskey V13 实例的账号"""

63
modules/user_config.py Normal file
View File

@ -0,0 +1,63 @@
import base64
import json
from pydantic import ValidationError
from pyrogram import Client, filters
from pyrogram.types import (
Message,
ReplyKeyboardMarkup,
KeyboardButton,
WebAppInfo,
ReplyKeyboardRemove,
)
from defs.web_app import WebAppData, WebAppUserConfig, filter_web_data
from glover import web_domain
from misskey_init import rerun_misskey_bot
from models.services.user_config import UserConfigAction
@Client.on_message(filters.incoming & filters.private & filter_web_data)
async def process_user_config(_, message: Message):
try:
data = WebAppData(**json.loads(message.web_app_data.data)).user_config
except (json.JSONDecodeError, ValidationError):
await message.reply("数据解析失败,请重试。", quote=True)
return
if user_config := await UserConfigAction.get_user_config_by_id(
message.from_user.id
):
user_config.timeline_spoiler = data.timeline_spoiler
user_config.push_spoiler = data.push_spoiler
await UserConfigAction.update_user_config(user_config)
else:
user_config = UserConfigAction.create_user_config(message.from_user.id)
user_config.timeline_spoiler = data.timeline_spoiler
user_config.push_spoiler = data.push_spoiler
await UserConfigAction.add_user_config(user_config)
await message.reply("更新设置成功。", quote=True, reply_markup=ReplyKeyboardRemove())
await rerun_misskey_bot(message.from_user.id)
async def get_user_config(user_id: int) -> str:
if user_config := await UserConfigAction.get_user_config_by_id(user_id):
data = WebAppUserConfig(
timeline_spoiler=user_config.timeline_spoiler,
push_spoiler=user_config.push_spoiler,
).json()
else:
data = "{}"
return base64.b64encode(data.encode()).decode()
@Client.on_message(filters.incoming & filters.private & filters.command(["config"]))
async def notice_user_config(_, message: Message):
data = await get_user_config(message.from_user.id)
url = f"https://{web_domain}/config?bot_data={data}"
await message.reply(
"请点击下方按钮,开始设置。",
quote=True,
reply_markup=ReplyKeyboardMarkup(
[[KeyboardButton(text="web config", web_app=WebAppInfo(url=url))]]
),
)