Commit b8f4198
Changed files (9)
src
llm
gemini
src/llm/gemini/text2img.py
@@ -15,7 +15,6 @@ from llm.contexts import get_conversations
from messages.progress import modify_progress
from messages.sender import send2tg
from messages.utils import remove_prefix
-from others.alias import command_alias
from utils import rand_number, strings_list
if TYPE_CHECKING:
@@ -96,7 +95,6 @@ async def gen_prompts(client: Client, message: Message) -> ContentListUnion:
role = "model" if any(m.content.startswith(f"🍌{TEXT2IMG.GEMINI_MODEL.title()}") for m in messages) else "user"
parts = []
for m in messages:
- m = command_alias(m) # noqa: PLW2901
try:
if m.photo:
buffer: BytesIO = await m.download(in_memory=True) # type: ignore
src/messages/help.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from config import PREFIX
+from permission import check_service
+
+
+def social_media_help(chat_id: int | str, ctype: str, prefix: str):
+ """Get the help message for social media preview."""
+ permission = check_service(cid=chat_id, ctype=ctype)
+ msg = f"🔗**链接解析**: {prefix}\n🔄使用 `/retry` 回复消息强制重试"
+ if permission["twitter"]:
+ msg += "\n🕊推特"
+ if permission["weibo"]:
+ msg += "\n🧣微博"
+ if permission["xhs"]:
+ msg += "\n🍠小红书"
+ if permission["douyin"]:
+ msg += "\n🎶抖音"
+ if permission["tiktok"]:
+ msg += "\n🎶TikTok"
+ if permission["instagram"]:
+ msg += "\n🏞Instagram"
+ if permission["music163"]:
+ msg += "\n🎧网易云音乐"
+ if permission["spotify"]:
+ msg += "\n🎧Spotify"
+ if permission["reddit"]:
+ msg += "\n🎈Reddit"
+ if permission["v2ex"]:
+ msg += "\n💻V2EX"
+ if permission["wechat"]:
+ msg += "\n🟢微信文章"
+ if permission["github"]:
+ msg += "\n📦GitHub"
+ if permission["ytdlp"]:
+ msg += "\n🔴油管"
+ msg += "\n🅱️哔哩哔哩"
+ msg += "\n🆕和所有yt-dlp支持的链接\n"
+ if permission["ai"]:
+ msg += f"\n🤖**AI对话**: `{PREFIX.GPT}`"
+ msg += f"\n🌠**AI生图**: `{PREFIX.GENIMG}` + 提示词"
+ msg += f"\n📖**AI总结**: 发送 `{PREFIX.AI_SUMMARY}` 查看详细教程"
+ if permission["asr"]:
+ msg += f"\n🗣**语音转文字**: `{PREFIX.ASR}` + 语音消息"
+ if permission["tts"]:
+ msg += f"\n🗣**文字转语音**: `{PREFIX.TTS}` + 文字"
+ if permission["audio"]:
+ msg += f"\n🎧**提取音频或语音**: `{PREFIX.AUDIO}` `{PREFIX.VOICE}` + 视频/语音消息"
+ if permission["ocr"]:
+ msg += f"\n🔤**图片转文字**: `{PREFIX.OCR}` + 图片消息"
+ if permission["price"]:
+ msg += f"\n💵**查询价格**: `{PREFIX.PRICE}` + symbol"
+ if permission["subtitle"]:
+ msg += f"\n📃**提取字幕**: `{PREFIX.SUBTITLE}` + B站或油管链接"
+ if permission["history"]:
+ msg += f"\n🗣**查询聊天记录**: 发送 `{PREFIX.HISTORY}` 查看详细教程"
+ if permission["wget"]:
+ msg += f"\n⏬**下载文件**: `{PREFIX.WGET}` + URL"
+ if permission["tmdb"]:
+ msg += f"\n🎬**查询影视信息**: `{PREFIX.TMDB}` + 关键词"
+ if permission["ytb"]:
+ msg += f"\n🔍**搜索YouTube**: `{PREFIX.SEARCH_YOUTUBE}` + 关键词"
+ if permission["google"]:
+ msg += f"\n🔍**搜索Google**: `{PREFIX.SEARCH_GOOGLE}` + 关键词"
+ if permission["danmu"]:
+ msg += f"\n📖**查询直播合订本**: 发送 `{PREFIX.DANMU}`, `{PREFIX.FAYAN}` 查看详细教程"
+ if permission["convert_chinese"]:
+ msg += f"\n🔄**简繁转换**: `{PREFIX.CONVERT_TO_SC}` 或 `{PREFIX.CONVERT_TO_TC}`"
+ if permission["ffmpeg"]:
+ msg += f"\n✂️**视频切片**: `{PREFIX.FFMPEG_CUT}` 回复视频消息"
+ msg += f"\n🎬**视频转码**: `{PREFIX.FFMPEG_H264}` 回复视频消息"
+ msg += f"\nℹ️**媒体信息**: `{PREFIX.FFPROBE}` 获取媒体信息" # noqa: RUF001
+ if permission["watermark"]:
+ msg += f"\n💧**添加水印**: `{PREFIX.WATERMARK}` 回复媒体消息"
+
+ msg += "\n\n单独发送每个命令前缀本身可查看该命令详细使用说明"
+ return msg
src/handler.py → src/messages/main.py
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import asyncio
-import re
from loguru import logger
from pyrogram.client import Client
@@ -9,29 +8,29 @@ from pyrogram.types import Message
from asr.voice_recognition import voice_to_text
from bridge.ocr import send_to_ocr_bridge
-from config import ENABLE, FAVORITE, PREFIX, PROXY
+from config import FAVORITE, PREFIX, PROXY
from danmu.entrypoint import query_danmu
from database.database import del_db
from history.query import query_chat_history
from llm.gpt import gpt_response
from llm.summary import ai_summary
+from messages.help import social_media_help
+from messages.modify import parse_kwargs
from messages.parser import parse_msg
from messages.sender import send2tg
from messages.utils import delete_message, equal_prefix, startswith_prefix
from networking import match_social_media_link
-from others.alias import command_alias
from others.convert_chinese import chinese_conversion
+from others.convert_img_file import convert_image_file
from others.download_external import download_url_in_message
from others.extract_audio import extract_audio_file
from others.favorite import save_favorite, send_favorite
from others.ffmpeg import ffmpeg_cut, ffmpeg_h264, ffprobe
-from others.raw_img_file import convert_raw_img_file
from others.search_google import search_google
from others.search_ytb import search_youtube
from others.tmdb import search_tmdb
from others.version import get_bot_version
from others.watermark import add_watermark
-from permission import check_service
from preview.bilibili import preview_bilibili
from preview.douyin import preview_douyin
from preview.github import preview_github
@@ -53,35 +52,35 @@ from ytdlp.main import preview_ytdlp
from ytdlp.utils import ProxyError
-async def handle_utilities(
+async def process_message(
client: Client,
message: Message,
target_chat: int | str | None = None,
reply_msg_id: int = 0,
*,
- ai: bool = True,
- asr: bool = True,
- audio: bool = True,
- danmu: bool = True,
- favorite: bool = True,
- google: bool = True,
- ocr: bool = True,
- history: bool = True,
- price: bool = True,
- subtitle: bool = True,
- summary: bool = True,
- tts: bool = True,
- quotly: bool = True,
- convert_chinese: bool = True,
- wget: bool = True,
- ytb: bool = True,
- tmdb: bool = True,
- ffmpeg: bool = True,
- watermark: bool = True,
- version: bool = True,
- raw_img: bool = True,
- show_progress: bool = True,
- detail_progress: bool = False,
+ ai: bool = True, # GPT Chat
+ asr: bool = True, # Voice to Text
+ audio_extract: bool = True, # Extract Audio from message. /audio -> m4a file. /voice -> wav file
+ danmu: bool = True, # Query Danmu database.
+ favorite: bool = True, # Send & Save message to favorite.
+ google_search: bool = True, # Search on Google.
+ ocr: bool = True, # OCR image file.
+ history: bool = True, # Query Chat History.
+ price: bool = True, # Query Stock / Fiat / Crypto price.
+ subtitle: bool = True, # Download YouTube subtitle.
+ summary: bool = True, # AI Chat Summary.
+ tts: bool = True, # Text to Speech.
+ quotly: bool = True, # Quote message.
+ convert_chinese: bool = True, # Traditional Chinese <-> Simplified Chinese.
+ wget: bool = True, # Download external URL.
+ ytb: bool = True, # Search on YouTube.
+ tmdb: bool = True, # Search on TMDB.
+ ffmpeg: bool = True, # FFMpeg processing
+ watermark: bool = True, # Add watermark to video.
+ version: bool = True, # Show bot version with dependencies.
+ convert_img: bool = True, # Convert image file.
+ show_progress: bool = True, # Show progress bar.
+ detail_progress: bool = False, # Show detailed progress bar.
**kwargs,
):
"""Call utility functions to handle the message.
@@ -93,44 +92,23 @@ async def handle_utilities(
reply_msg_id (int, optional): If set to integer > 0, the result is sent as a reply message to this message_id.
If set to 0, reply to the trigger message itself.
If set to -1, do not send as a reply message.
- ai (bool, optional): Enable GPT. Defaults to True.
- asr (bool, optional): Enable ASR. Defaults to True.
- audio (bool, optional): Enable Video -> Audio. Defaults to True.
- danmu (bool, optional): Enable Query Danmu database. Defaults to True.
- favorite (bool, optional): Enable Send & Save message to favorite. Defaults to True.
- google (bool, optional): Enable Google Search. Defaults to True.
- ytb (bool, optional): Enable YouTube Search. Defaults to True.
- history (bool, optional): Enable History Search. Defaults to True.
- subtitle (bool, optional): Enable YouTube subtitle. Defaults to True.
- tts (bool, optional): Enable TTS. Defaults to True.
- quotly (bool, optional): Enable Quote message. Defaults to True.
- convert_chinese (bool, optional): Enable Traditional Chinese <-> Simplified Chinese. Defaults to True.
- wget (bool, optional): Enable WGET. Defaults to True.
- ocr (bool, optional): Enable OCR. Defaults to True.
- price (bool, optional): Enable Asset price. Defaults to True.
- summary (bool, optional): Enable AI summary. Defaults to True.
- tmdb (bool, optional): Enable TMDB query. Defaults to True.
- ffmpeg (bool, optional): Enable ffmpeg commands. Defaults to True.
- watermark (bool, optional): Enable watermark commands. Defaults to True.
- raw_img (bool, optional): Enable convert raw image. Defaults to False.
- show_progress (bool, optional): Show a progress message on Telegram. Defaults to True.
- detail_progress (bool, optional): Show detailed progress (Only if show_proress is set to True). Defaults to False.
"""
kwargs |= {"target_chat": target_chat, "reply_msg_id": reply_msg_id, "show_progress": show_progress, "detail_progress": detail_progress}
- message = command_alias(message)
- info = parse_msg(message)
- kwargs |= params_from_msg_text(info["text"]) # merge the parameters from the message text
+ if true(kwargs.get("target_chat")):
+ kwargs["target_chat"] = to_int(kwargs["target_chat"])
+ message, msg_kwargs = parse_kwargs(message)
+ kwargs |= msg_kwargs # merge the kwargs from the message text
if ai:
await gpt_response(client, message, **kwargs) # /ai
if asr:
await voice_to_text(client, message, **kwargs) # /asr
- if audio:
+ if audio_extract:
await extract_audio_file(client, message, **kwargs) # /audio
if subtitle:
await get_subtitle(client, message, **kwargs) # /subtitle
if wget:
await download_url_in_message(client, message, **kwargs) # /wget
- if google:
+ if google_search:
await search_google(client, message, **kwargs) # /google
if ytb:
await search_youtube(client, message, **kwargs) # /ytb
@@ -155,8 +133,8 @@ async def handle_utilities(
await quote_message(client, message, **kwargs) # /quote
if tmdb:
await search_tmdb(client, message, **kwargs) # /tmdb
- if raw_img:
- await convert_raw_img_file(client, message, **kwargs)
+ if convert_img:
+ await convert_image_file(client, message, **kwargs)
if ffmpeg:
await ffmpeg_cut(client, message, **kwargs)
await ffmpeg_h264(client, message, **kwargs)
@@ -166,30 +144,27 @@ async def handle_utilities(
if version:
await get_bot_version(client, message, **kwargs)
+ await preview_social_media(client, message, **kwargs)
-async def handle_social_media(
+
+async def preview_social_media(
client: Client,
message: Message,
- target_chat: int | str | None = None,
- reply_msg_id: int = 0,
*,
- need_prefix: bool = True,
- cmd_prefix: str | None = None,
+ need_prefix: bool = True, # Need prefix to parse social media. (/dl)
prepend_sender_user: bool = False,
- douyin: bool = True,
- tiktok: bool = True,
- instagram: bool = True,
- twitter: bool = True,
- weibo: bool = True,
- reddit: bool = True,
- github: bool = True,
- xhs: bool = True,
- v2ex: bool = True,
- music163: bool = True,
- spotify: bool = True,
- ytdlp: bool = True,
- show_progress: bool = True,
- detail_progress: bool = False,
+ douyin: bool = True, # Parse Douyin
+ tiktok: bool = True, # Parse TikTok
+ instagram: bool = True, # Parse Instagram
+ twitter: bool = True, # Parse Twitter
+ weibo: bool = True, # Parse Weibo
+ reddit: bool = True, # Parse Reddit
+ github: bool = True, # Parse Github
+ xhs: bool = True, # Parse XiaoHongShu
+ v2ex: bool = True, # Parse V2EX
+ music163: bool = True, # Parse Music163
+ spotify: bool = True, # Parse Spotify
+ ytdlp: bool = True, # Parse YT-DLP
**kwargs,
):
"""Preview social media link in the message.
@@ -207,12 +182,7 @@ async def handle_social_media(
show_progress (bool, optional): Show a progress message on Telegram. Defaults to True.
detail_progress (bool, optional): Show detailed progress (Only if show_proress is set to True). Defaults to False.
"""
- kwargs |= {"target_chat": target_chat, "reply_msg_id": reply_msg_id, "show_progress": show_progress, "detail_progress": detail_progress}
- if not ENABLE.SEND_AS_REPLY:
- kwargs["reply_msg_id"] = -1
- if cmd_prefix is None:
- cmd_prefix = PREFIX.MAIN
- # these commands are handled in `handle_utilities`
+ # these commands are handled in `process_message`
ignore_prefix = [
PREFIX.ASR,
PREFIX.AI_SUMMARY,
@@ -245,36 +215,33 @@ async def handle_social_media(
this_texts = info["text"] # texts of the trigger message
if startswith_prefix(this_texts, prefix=ignore_prefix):
return None
- if need_prefix and not startswith_prefix(this_texts, prefix=[cmd_prefix, "/help", "/retry"]):
+ if need_prefix and not startswith_prefix(this_texts, prefix=[PREFIX.SOCIAL_MEDIA, "/help", "/retry"]):
return None
- kwargs |= params_from_msg_text(this_texts) # merge the parameters from the message text
- if true(kwargs.get("target_chat")):
- kwargs["target_chat"] = to_int(kwargs["target_chat"])
+
# message only contains prefix command
- if equal_prefix(this_texts, prefix=[cmd_prefix, "/help", "/retry"]):
+ if equal_prefix(this_texts, prefix=[PREFIX.SOCIAL_MEDIA, "/help", "/retry"]):
# without reply, send docs if message only contains prefix command
if not message.reply_to_message:
- help_msg = get_social_media_help(info["cid"], info["ctype"], cmd_prefix)
+ help_msg = social_media_help(info["cid"], info["ctype"], PREFIX.SOCIAL_MEDIA)
return await send2tg(client, message, texts=help_msg, **kwargs)
# with reply, treat the reply_msg as the trigger to preview social media link
message = message.reply_to_message
info = parse_msg(message, silent=True) # parse again
- warn_msg = None
- if not need_prefix and startswith_prefix(this_texts, prefix=cmd_prefix):
- warn_msg = await client.send_message(info["cid"], text="⚠️本会话中可直接发送链接, 无需添加命令前缀\n⚠️No need to add command prefix in this chat.")
-
# add send_from_user.
if prepend_sender_user:
# Caution: this format should be consistent with `save_messages` function in `message.database.py`
kwargs["send_from_user"] = f"👤[@{info['full_name']}](tg://user?id={info['uid']})//"
+
+ warn_msg = None
try:
- matched = await match_social_media_link(info["text"]) # match "platform" and "url" (and other info)
+ matched = await match_social_media_link(this_texts) # match "platform" and "url" (and other info)
if matched["platform"]:
logger.success(f"Matched: {matched}")
kwargs |= matched
if startswith_prefix(this_texts, prefix="/retry"):
await del_db(matched["db_key"])
+
if douyin and matched["platform"] == "douyin":
return await preview_douyin(client, message, **kwargs)
if tiktok and matched["platform"] == "tiktok":
@@ -301,17 +268,16 @@ async def handle_social_media(
return await preview_v2ex(client, message, **kwargs)
if matched["platform"].startswith("bilibili-"): # this is not bilibili video, for videos, use yt-dlp
return await preview_bilibili(client, message, **kwargs)
+
sent_messages = []
try:
- if ytdlp and any(matched["platform"] == x for x in ["bilibili", "youtube", "ytdlp"]):
+ if ytdlp and matched["platform"] in ["bilibili", "youtube", "ytdlp"]:
sent_messages = await preview_ytdlp(client, message, **kwargs)
except ProxyError:
logger.error(f"🚫{matched['platform']}代理错误")
if PROXY.YTDLP_FALLBACK:
logger.warning(f"🔄使用备用代理{PROXY.YTDLP_FALLBACK}")
sent_messages = await preview_ytdlp(client, message, proxy=PROXY.YTDLP_FALLBACK, **kwargs)
- if warn_msg:
- await warn_msg.delete()
if not sent_messages and any(keyword in info["text"] for keyword in ["facebook.com", "threads.com", "youtube.com", "bilibili.com"]):
if kwargs.get("show_progress"):
@@ -321,7 +287,7 @@ async def handle_social_media(
# if ytdlp failed, try to download directly
elif (
not sent_messages
- and startswith_prefix(this_texts, prefix=cmd_prefix)
+ and startswith_prefix(this_texts, prefix=PREFIX.SOCIAL_MEDIA)
and matched["platform"]
not in [
"bilibili",
@@ -340,124 +306,10 @@ async def handle_social_media(
]
):
if kwargs.get("show_progress"):
- kwargs["progress"] = await client.send_message(info["cid"], text="⚠️暂时不支持解析链接, 尝试直接下载该网页")
- await download_url_in_message(client, this_msg, extra_prefix=cmd_prefix, **kwargs)
+ warn_msg = await client.send_message(info["cid"], text="⚠️暂时不支持解析链接, 尝试直接下载该网页")
+ await download_url_in_message(client, this_msg, extra_prefix=PREFIX.SOCIAL_MEDIA, **kwargs)
except Exception as e:
logger.exception(e)
-
-
-def params_from_msg_text(texts: str | None = None) -> dict:
- """Get the parameters from the message text.
-
- Texts can contain some special words to temporarily enable / disable some features.
- The parsed options will be merged into kwargs.
-
- Currently, three formats are supported:
- 1. #no_xx: kwargs["xx"] = False
- 2. #with_xx: kwargs["xx"] = True
- 3. #set_xx=var: kwargs["xx"] = var
-
- Example text: #no_ytdlp_send_video #set_douyin_provider=tikhub
-
- Args:
- texts (str | None, optional): The message text.
- """
- params = {}
- if not texts:
- return params
-
- # Pattern 1: #no_xx -> kwargs["xx"] = False
- for match in re.findall(r"#no_(\w+)", texts, re.IGNORECASE):
- params[match.lower()] = False
-
- # Pattern 2: #with_xx -> kwargs["xx"] = True
- for match in re.findall(r"#with_(\w+)", texts, re.IGNORECASE):
- params[match.lower()] = True
-
- # Pattern 3: #set_xx=var -> kwargs["xx"] = var
- for match in re.findall(r"#set_(\w+)=([^,。#\s]+)", texts, re.IGNORECASE): # noqa: RUF001
- key, value = match
- if str(value).lower() in ["none", "null"]:
- value = None
- params[key.lower()] = value
- if params:
- logger.info(f"🔧手动设置参数: {params}")
- return params
-
-
-def get_social_media_help(chat_id: int | str, ctype: str, prefix: str):
- """Get the help message for social media preview."""
- permission = check_service(cid=chat_id, ctype=ctype)
- msg = f"🔗**链接解析**: {prefix}\n🔄使用 `/retry` 回复消息强制重试"
- if permission["twitter"]:
- msg += "\n🕊推特"
- if permission["weibo"]:
- msg += "\n🧣微博"
- if permission["xhs"]:
- msg += "\n🍠小红书"
- if permission["douyin"]:
- msg += "\n🎶抖音"
- if permission["tiktok"]:
- msg += "\n🎶TikTok"
- if permission["instagram"]:
- msg += "\n🏞Instagram"
- if permission["music163"]:
- msg += "\n🎧网易云音乐"
- if permission["spotify"]:
- msg += "\n🎧Spotify"
- if permission["reddit"]:
- msg += "\n🎈Reddit"
- if permission["v2ex"]:
- msg += "\n💻V2EX"
- if permission["wechat"]:
- msg += "\n🟢微信文章"
- if permission["github"]:
- msg += "\n📦GitHub"
- if permission["ytdlp"]:
- msg += "\n🔴油管"
- msg += "\n🅱️哔哩哔哩"
- msg += "\n🆕和所有yt-dlp支持的链接\n"
- if permission["ai"]:
- msg += f"\n🤖**AI对话**: `{PREFIX.GPT}`"
- msg += f"\n🌠**AI生图**: `{PREFIX.GENIMG}` + 提示词"
- msg += f"\n📖**AI总结**: 发送 `{PREFIX.AI_SUMMARY}` 查看详细教程"
- if permission["asr"]:
- msg += f"\n🗣**语音转文字**: `{PREFIX.ASR}` + 语音消息"
- if permission["tts"]:
- msg += f"\n🗣**文字转语音**: `{PREFIX.TTS}` + 文字"
- if permission["audio"]:
- msg += f"\n🎧**提取音频或语音**: `{PREFIX.AUDIO}` `{PREFIX.VOICE}` + 视频/语音消息"
- if permission["ocr"]:
- msg += f"\n🔤**图片转文字**: `{PREFIX.OCR}` + 图片消息"
- if permission["price"]:
- msg += f"\n💵**查询价格**: `{PREFIX.PRICE}` + symbol"
- if permission["subtitle"]:
- msg += f"\n📃**提取字幕**: `{PREFIX.SUBTITLE}` + B站或油管链接"
- if permission["history"]:
- msg += f"\n🗣**查询聊天记录**: 发送 `{PREFIX.HISTORY}` 查看详细教程"
- if permission["wget"]:
- msg += f"\n⏬**下载文件**: `{PREFIX.WGET}` + URL"
- if permission["tmdb"]:
- msg += f"\n🎬**查询影视信息**: `{PREFIX.TMDB}` + 关键词"
- if permission["ytb"]:
- msg += f"\n🔍**搜索YouTube**: `{PREFIX.SEARCH_YOUTUBE}` + 关键词"
- if permission["google"]:
- msg += f"\n🔍**搜索Google**: `{PREFIX.SEARCH_GOOGLE}` + 关键词"
- if permission["danmu"]:
- msg += f"\n📖**查询直播合订本**: 发送 `{PREFIX.DANMU}`, `{PREFIX.FAYAN}` 查看详细教程"
- if permission["convert_chinese"]:
- msg += f"\n🔄**简繁转换**: `{PREFIX.CONVERT_TO_SC}` 或 `{PREFIX.CONVERT_TO_TC}`"
- if permission["ffmpeg"]:
- msg += f"\n✂️**视频切片**: `{PREFIX.FFMPEG_CUT}` 回复视频消息"
- msg += f"\n🎬**视频转码**: `{PREFIX.FFMPEG_H264}` 回复视频消息"
- msg += f"\nℹ️**媒体信息**: `{PREFIX.FFPROBE}` 获取媒体信息" # noqa: RUF001
- if permission["watermark"]:
- msg += f"\n💧**添加水印**: `{PREFIX.WATERMARK}` 回复媒体消息"
-
- msg += "\n\n单独发送每个命令前缀本身可查看该命令详细使用说明"
- return msg
-
-
-if __name__ == "__main__":
- params_from_msg_text("#with_1 #WITH_x #NO_1 #no_2 #set_yy=3, #no_fetch_douyin_comments #set_douyin_provider=tikhub #set_reply_msg_id=None")
+ finally:
+ await delete_message(warn_msg)
src/messages/modify.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import re
+
+from glom import glom
+from loguru import logger
+from pyrogram.types import Message
+from pyrogram.types.messages_and_media.message import Str
+
+from utils import slim_cid
+
+
+def message_modify(message: Message) -> Message:
+ """This function modifies the message via environment variable before any subsequent processing.
+
+ Args:
+ message (Message): The message to be modified.
+
+ Returns:
+ Message: The modified message.
+ """
+ message = add_prefix_suffix(message)
+ return replace_prefix(message)
+
+
+def add_prefix_suffix(message: Message) -> Message:
+ """This function add prefix and suffix to the message via environment variable.
+
+ Environment Variables:
+ MOD_MSG_ADD_PREFIX_C{CID}_U{UID} (str): Add prefix to message of {CID} and {UID}
+ MOD_MSG_ADD_SUFFIX_C{CID}_U{UID} (str): Add suffix to message of {CID} and {UID}
+ MOD_MSG_ADD_PREFIX_C{CID} (str): Add prefix to message of {CID}
+ MOD_MSG_ADD_SUFFIX_C{CID} (str): Add suffix to message of {CID}
+ MOD_MSG_ADD_PREFIX_U{UID} (str): Add prefix to message of {UID}
+ MOD_MSG_ADD_SUFFIX_U{UID} (str): Add suffix to message of {UID}
+
+ If there are multiple envvars for the same message, they will be applied in the order of
+ Chat Level -> User Level -> Chat_User Level
+
+ Example:
+ Suppose the message is Message(chat_id=111, from_user=User(id=222), content="hello")
+
+ # Single Environment Variable:
+ MOD_MSG_ADD_PREFIX_C111 = "foo" -> "foo hello" for chat_id=111
+ MOD_MSG_ADD_SUFFIX_C111 = "bar" -> "hello bar" for chat_id=111
+ MOD_MSG_ADD_PREFIX_U222 = "foo" -> "foo hello" for user_id=222
+ MOD_MSG_ADD_SUFFIX_U222 = "bar" -> "hello bar" for user_id=222
+ MOD_MSG_ADD_PREFIX_C111_U222 = "foo" -> "foo hello" for chat_id=111 and user_id=222
+ MOD_MSG_ADD_SUFFIX_C111_U222 = "bar" -> "hello bar" for chat_id=111 and user_id=222
+
+ # Multiple Environment Variables:
+ MOD_MSG_ADD_PREFIX_C111 = "foo" + MOD_MSG_ADD_PREFIX_C111_U222 = "bar"
+ -> "bar foo hello" (Chat level prefix `foo`, then Chat_User level prefix `bar`)
+
+ MOD_MSG_ADD_SUFFIX_U111 = "foo" + MOD_MSG_ADD_PREFIX_C111 = "bar"
+ -> "bar hello foo" (Chat level prefix `bar`, then User level suffix `foo`)
+
+
+ Args:
+ message (Message): The message to be modified.
+
+ Returns:
+ Message: The modified message.
+ """
+ uid = glom(message, "from_user.id", default=0) or 0
+ cid = glom(message, "chat.id", default=0) or 0
+ cid = slim_cid(cid)
+ texts = str(message.content).strip()
+ # Chat level
+ suffix = os.getenv(f"MOD_MSG_ADD_SUFFIX_C{cid}")
+ if prefix := os.getenv(f"MOD_MSG_ADD_PREFIX_C{cid}"):
+ texts = f"{prefix} {texts}"
+ if suffix := os.getenv(f"MOD_MSG_ADD_SUFFIX_C{cid}"):
+ texts = f"{texts} {suffix}"
+
+ # User level
+ if prefix := os.getenv(f"MOD_MSG_ADD_PREFIX_U{uid}"):
+ texts = f"{prefix} {texts}"
+ if suffix := os.getenv(f"MOD_MSG_ADD_SUFFIX_U{uid}"):
+ texts = f"{texts} {suffix}"
+
+ # Chat_User level
+ if prefix := os.getenv(f"MOD_MSG_ADD_PREFIX_C{cid}_U{uid}"):
+ texts = f"{prefix} {texts}"
+ if suffix := os.getenv(f"MOD_MSG_ADD_SUFFIX_C{cid}_U{uid}"):
+ texts = f"{texts} {suffix}"
+ texts = str(message.content).strip()
+
+ if texts != str(message.content).strip(): # If the message is modified
+ if message.text:
+ message.text = Str(texts)
+ elif message.caption:
+ message.caption = Str(texts)
+ return message
+
+
+def replace_prefix(message: Message) -> Message:
+ """This function replace prefix of the message via environment variables.
+
+ Environment Variables:
+ MOD_MSG_REPLACE_PREFIX_GLOBAL_{prefix} (str): Replace prefix to all messages (prefix is case-sensitive)
+ MOD_MSG_REPLACE_PREFIX_C{CID}_{prefix} (str): Replace prefix to message of {CID} (prefix is case-sensitive)
+ MOD_MSG_REPLACE_PREFIX_U{UID}_{prefix} (str): Replace prefix to message of {UID} (prefix is case-sensitive)
+ MOD_MSG_REPLACE_PREFIX_CU{CID}_{UID}_{prefix} (str): Replace prefix to message of {CID} and {UID} (prefix is case-sensitive)
+
+ If there are multiple envvars for the same message, they will be applied in the order of
+ Global Level -> Chat Level -> User Level -> Chat_User Level
+
+ Example:
+ Suppose the message is Message(chat_id=111, from_user=User(id=222), content="this is a sample msg")
+ # Single Environment Variable:
+ MOD_MSG_REPLACE_PREFIX_GLOBAL_this = "foo" -> "foo is a sample msg" for all messages
+ MOD_MSG_REPLACE_PREFIX_C111_this = "foo" -> "foo is a sample msg" for chat_id=111
+ MOD_MSG_REPLACE_PREFIX_U222_this__SPACE__ = "foo" -> "foois a sample msg" for user_id=222
+ MOD_MSG_REPLACE_PREFIX_CU111_222_this__SPACE__is = "foo" -> "fooa sample msg" for chat_id=111 and user_id=222
+
+ # Multiple Environment Variables:
+ MOD_MSG_REPLACE_PREFIX_GLOBAL_this = "foo" + MOD_MSG_REPLACE_PREFIX_C111_foo = "bar"
+ -> "bar is a sample msg" (Global level `this` -> `foo`, then Chat level `foo` -> `bar`)
+
+ Args:
+ message (Message): The message to be modified.
+
+ Returns:
+ Message: The modified message.
+ """
+ uid = glom(message, "from_user.id", default=0) or 0
+ cid = glom(message, "chat.id", default=0) or 0
+ texts = str(message.content).strip()
+
+ # Global level
+ global_envs = [x for x in os.environ if x.upper().startswith("MOD_MSG_REPLACE_PREFIX_GLOBAL_")]
+ for env in sorted(global_envs, key=lambda x: len(x), reverse=True): # sorted by length in descending order
+ env_var = os.environ[env]
+ prefix = env[len("MOD_MSG_REPLACE_PREFIX_GLOBAL_") :]
+ prefix = escape_strings(prefix)
+ env_var = escape_strings(env_var)
+ if texts.startswith(prefix):
+ texts = texts.replace(prefix, env_var, count=1)
+ logger.warning(f"Global level prefix `{prefix} = {env_var}` -> {texts}")
+
+ # Chat level
+ chat_envs = [x for x in os.environ if x.upper().startswith(f"MOD_MSG_REPLACE_PREFIX_C{cid}_")]
+ for env in sorted(chat_envs, key=lambda x: len(x), reverse=True):
+ env_var = os.environ[env]
+ prefix = env[len(f"MOD_MSG_REPLACE_PREFIX_C{cid}_") :]
+ prefix = escape_strings(prefix)
+ env_var = escape_strings(env_var)
+ if texts.startswith(prefix):
+ texts = texts.replace(prefix, env_var, count=1)
+ logger.warning(f"Chat ({cid}) level prefix `{prefix} = {env_var}` -> {texts}")
+
+ # User level
+ user_envs = [x for x in os.environ if x.upper().startswith(f"MOD_MSG_REPLACE_PREFIX_U{uid}_")]
+ for env in sorted(user_envs, key=lambda x: len(x), reverse=True):
+ env_var = os.environ[env]
+ prefix = env[len(f"MOD_MSG_REPLACE_PREFIX_U{uid}_") :]
+ prefix = escape_strings(prefix)
+ env_var = escape_strings(env_var)
+ if texts.startswith(prefix):
+ texts = texts.replace(prefix, env_var, count=1)
+ logger.warning(f"User ({uid}) level prefix `{prefix} = {env_var}` -> {texts}")
+
+ # Chat_User level
+ chat_user_envs = [x for x in os.environ if x.upper().startswith(f"MOD_MSG_REPLACE_PREFIX_CU{cid}_{uid}_")]
+ for env in sorted(chat_user_envs, key=lambda x: len(x), reverse=True):
+ env_var = os.environ[env]
+ prefix = env[len(f"MOD_MSG_REPLACE_PREFIX_CU{cid}_{uid}_") :]
+ prefix = escape_strings(prefix)
+ env_var = escape_strings(env_var)
+ if texts.startswith(prefix):
+ texts = texts.replace(prefix, env_var, count=1)
+ logger.warning(f"Chat ({cid}), User ({uid}) level prefix `{prefix} = {env_var}` -> {texts}")
+
+ if texts != str(message.content).strip(): # If the message is modified
+ if message.text:
+ message.text = Str(texts)
+ elif message.caption:
+ message.caption = Str(texts)
+ return message
+
+
+STRING_MAP = {
+ "__DASH__": "-",
+ "__SPACE__": " ",
+ "__COLON__": ":",
+ "__PERIOD__": ".",
+ "__COMMA__": ",",
+ "__SEMICOLON__": ";",
+ "__EXCLAMATION__": "!",
+ "__QUESTION__": "?",
+ "__LEFT_PARENTHESIS__": "(",
+ "__RIGHT_PARENTHESIS__": ")",
+ "__LEFT_BRACKET__": "[",
+ "__RIGHT_BRACKET__": "]",
+ "__LEFT_BRACE__": "{",
+ "__RIGHT_BRACE__": "}",
+ "__SLASH__": "/",
+ "__ASTERISK__": "*",
+ "__POUND__": "#",
+ "__NEWLINE__": "\n",
+ "__AT__": "@",
+ "__DOLLAR__": "$",
+ "__PERCENT__": "%",
+ "__CARET__": "^",
+ "__AMPERSAND__": "&",
+ "__UNDERSCORE__": "_",
+ "__PLUS__": "+",
+ "__EQUALS__": "=",
+ "__PIPE__": "|",
+ "__BACKSLASH__": "\\",
+ "__BACKTICK__": "`",
+ "__QUOTE__": "'",
+ "__DOUBLE_QUOTE__": '"',
+ "__LESS_THAN__": "<",
+ "__GREATER_THAN__": ">",
+ "__TILDE__": "~",
+}
+STRING_RE_PATTERN = re.compile("|".join(re.escape(k) for k in STRING_MAP))
+
+
+def escape_strings(s: str) -> str:
+ """Escape some special characters.
+
+ "foo__DASH__bar" -> "foo-bar"
+ "http__COLON____SLASH____SLASH__www__DOT__example__DOT__com" -> "http://www.example.com"
+ """
+ if "__" not in s:
+ return s
+ return STRING_RE_PATTERN.sub(lambda m: STRING_MAP[m.group(0)], s)
+
+
+def parse_kwargs(message: Message) -> tuple[Message, dict]:
+ """Parse the kwargs from the message text.
+
+ Currently, three formats are supported:
+ 1. #no_xx: kwargs["xx"] = False
+ 2. #with_xx: kwargs["xx"] = True
+ 3. #set_xx=var: kwargs["xx"] = var
+
+ Example:
+ Suppose the message is Message(text="sample texts #no_ytdlp_send_video #set_douyin_provider=tikhub")
+ Then the kwargs will be:
+ kwargs = {
+ "ytdlp_send_video": False,
+ "douyin_provider": "tikhub",
+ }
+ """
+ if not isinstance(message, Message):
+ return message, {}
+
+ if not message.content:
+ return message, {}
+
+ kwargs = {}
+ texts = str(message.content)
+ # Pattern 1: #no_xx -> kwargs["xx"] = False
+ for match in re.findall(r"#no_(\w+)", texts, flags=re.IGNORECASE):
+ kwargs[match.lower()] = False
+ texts = re.sub(rf"#no_{match}", "", texts, flags=re.IGNORECASE)
+
+ # Pattern 2: #with_xx -> kwargs["xx"] = True
+ for match in re.findall(r"#with_(\w+)", texts, re.IGNORECASE):
+ kwargs[match.lower()] = True
+ texts = re.sub(rf"#with_{match}", "", texts, flags=re.IGNORECASE)
+
+ # Pattern 3: #set_xx=var -> kwargs["xx"] = var
+ for match in re.findall(r"#set_(\w+)=([^#\s]+)", texts, re.IGNORECASE):
+ key, value = match
+ texts = re.sub(rf"#set_{key}={value}", "", texts, flags=re.IGNORECASE)
+ if str(value).lower() in ["none", "null"]:
+ value = None
+ kwargs[key.lower()] = value
+ if kwargs:
+ logger.info(f"🔧手动设置参数: {kwargs}")
+ if message.text:
+ message.text = Str(texts)
+ elif message.caption:
+ message.caption = Str(texts)
+ return message, kwargs
src/others/alias.py
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-from pyrogram.types import Message
-from pyrogram.types.messages_and_media.message import Str
-
-from config import PREFIX
-
-
-def command_alias(message: Message) -> Message:
- texts = message.content.lstrip()
- # AI image generation
- if texts.startswith("/flux"):
- texts = texts.replace("/flux", f"{PREFIX.GENIMG} @flux")
- elif texts.startswith("/sd"):
- texts = texts.replace("/sd", f"{PREFIX.GENIMG} @doubao")
- elif texts.startswith("/stable"):
- texts = texts.replace("/stable", f"{PREFIX.GENIMG} @sd")
- elif texts.startswith("/nano"):
- texts = texts.replace("/nano", f"{PREFIX.GENIMG} @gemini")
- elif texts.startswith("/z"):
- texts = texts.replace("/z", f"{PREFIX.GENIMG} @zimage")
-
- if message.text:
- message.text = Str(texts)
- elif message.caption:
- message.caption = Str(texts)
- return message
src/others/raw_img_file.py → src/others/convert_img_file.py
@@ -17,7 +17,7 @@ HELP = f"""🔁**原图转图片**
"""
-async def convert_raw_img_file(client: Client, message: Message, *, convert_need_prefix: bool | None = None, **kwargs):
+async def convert_image_file(client: Client, message: Message, *, convert_need_prefix: bool | None = None, **kwargs):
# send docs if message == "/convert", without reply
if equal_prefix(message.text, prefix=[PREFIX.CONVERT]) and not message.reply_to_message:
await send2tg(client, message, texts=HELP, **kwargs)
src/config.py
@@ -80,7 +80,7 @@ class ENABLE: # see fine-grained permission in `src/permission.py`
class PREFIX:
- MAIN = os.getenv("PREFIX_MAIN", "/benny, /dl, !dl")
+ SOCIAL_MEDIA = os.getenv("PREFIX_SOCIAL_MEDIA", "/benny, /dl, !dl")
AI_SUMMARY = os.getenv("PREFIX_AI_SUMMARY", "/summary").lower()
ASR = os.getenv("PREFIX_ASR", "/asr").lower()
AUDIO = os.getenv("PREFIX_AUDIO", "/audio").lower()
src/main.py
@@ -22,10 +22,10 @@ from bridge.ocr import forward_ocr_results
from bridge.social import forward_social_media_results
from config import DAILY_MESSAGES, DEVICE_NAME, ENABLE, PROXY, TOKEN, TZ, cache
from danmu.sync import sync_livechats
-from handler import handle_social_media, handle_utilities
from history.sync import backup_chat_history, sync_chat_history
from llm.summary import daily_summary
from llm.utils import clean_gemini_files
+from messages.main import process_message
from messages.parser import parse_msg
from permission import check_permission
from podcast.main import summary_pods
@@ -54,16 +54,14 @@ async def main():
permission = await check_permission(client, message)
if permission["disabled"]:
return
- await handle_utilities(client, message, **permission)
- await handle_social_media(client, message, **permission)
+ await process_message(client, message, **permission)
@app.on_message(filters.channel)
async def channels(client: Client, message: Message):
permission = await check_permission(client, message)
if permission["disabled"]:
return
- await handle_utilities(client, message, **permission)
- await handle_social_media(client, message, **permission)
+ await process_message(client, message, **permission)
@app.on_message(filters.bot)
async def bots(client: Client, message: Message):
@@ -74,8 +72,7 @@ async def main():
await forward_social_media_results(client, message)
await forward_ocr_results(client, message)
await forward_chartimg_results(client, message)
- await handle_utilities(client, message, **permission)
- await handle_social_media(client, message, **permission)
+ await process_message(client, message, **permission)
# filters.private = {user chats + bot chats}
# so the private handler should be placed after the bot handler
@@ -89,8 +86,7 @@ async def main():
if permission["disabled"]:
return
parse_msg(message, verbose=True)
- await handle_utilities(client, message, **permission)
- await handle_social_media(client, message, **permission)
+ await process_message(client, message, **permission)
@app.on_message(group=1)
@app.on_edited_message(group=1)
src/permission.py
@@ -8,11 +8,13 @@ from pyrogram.client import Client
from pyrogram.types import Message
from config import ENABLE, TID, cache
+from messages.modify import message_modify
from utils import i_am_bot, slim_cid, to_int, true
async def check_permission(client: Client, message: Message) -> dict:
"""Check if the user has permission to use the bot."""
+ message = message_modify(message)
ctype = message.chat.type.name if message.chat and message.chat.type else ""
ctype = ctype.upper().removeprefix("SUPER") # SUPERGROUP -> GROUP
@@ -116,16 +118,16 @@ def check_service(cid: int | str, ctype: str) -> dict:
"need_prefix": True,
"ai": True,
"asr": True,
- "audio": True,
+ "audio_extract": True,
"danmu": True,
"subtitle": True,
"wget": True,
"ocr": True,
"price": True,
- "raw_img": True,
+ "convert_img": True,
"tts": True,
"ytb": True,
- "google": True,
+ "google_search": True,
"show_progress": True,
"detail_progress": True,
"douyin": True,
@@ -186,13 +188,13 @@ def check_service(cid: int | str, ctype: str) -> dict:
if not ENABLE.ASR:
permission["asr"] = False
if not ENABLE.AUDIO:
- permission["audio"] = False
+ permission["audio_extract"] = False
if not ENABLE.SUBTITLE:
permission["subtitle"] = False
if not ENABLE.SEARCH_YOUTUBE:
permission["ytb"] = False
if not ENABLE.SEARCH_GOOGLE:
- permission["google"] = False
+ permission["google_search"] = False
if not ENABLE.WGET:
permission["wget"] = False
if not ENABLE.OCR:
@@ -200,7 +202,7 @@ def check_service(cid: int | str, ctype: str) -> dict:
if not ENABLE.PRICE:
permission["price"] = False
if not ENABLE.RAW_IMG_CONVERT:
- permission["raw_img"] = False
+ permission["convert_img"] = False
if not ENABLE.QUERY_DANMU:
permission["danmu"] = False
if not ENABLE.HISTORY: