Commit b8f4198

benny-dou <60535774+benny-dou@users.noreply.github.com>
2026-01-07 10:16:18
feat(alias): support universal message modification via envvars
1 parent 43131dc
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: