mirror of
https://github.com/Xtao-Labs/misskey2telegram.git
synced 2024-11-24 22:43:35 +00:00
feat: support spoiler
This commit is contained in:
parent
efbe97cd37
commit
a02022d3a3
36
alembic/versions/3cbe5fbdb7e3_config.py
Normal file
36
alembic/versions/3cbe5fbdb7e3_config.py
Normal 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 ###
|
@ -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
26
defs/web_app.py
Normal 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)
|
@ -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)
|
||||||
|
11
models/models/user_config.py
Normal file
11
models/models/user_config.py
Normal 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)
|
36
models/services/user_config.py
Normal file
36
models/services/user_config.py
Normal 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)
|
@ -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
63
modules/user_config.py
Normal 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))]]
|
||||||
|
),
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user