main
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3import contextlib
  4
  5from loguru import logger
  6from pyrogram.client import Client
  7from pyrogram.types import Message, ReplyParameters
  8
  9from config import cache
 10from messages.parser import parse_msg
 11from utils import i_am_bot
 12
 13SOCIAL_BOTS = {
 14    "ParsehubBot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 15    "GLBetabot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 16    "douyin_download_bot": ["douyin", "tiktok", "weibo", "x", "xiaohongshu"],
 17    "bilibiliparse_bot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 18    "web2album_bot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 19    "icbcbot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 20    "KyDownloaderBot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 21    "DouYintg_bot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 22    "MultiSaverXbot": ["douyin", "tiktok", "instagram", "weibo", "x", "xiaohongshu"],
 23    "Music163bot": ["music163", "spotify"],
 24}
 25
 26CAPTIONS = {"MultiSaverXbot": ""}
 27MEDIA_TYPES = {"Music163bot": ["audio"]}  # default: ['video', 'photo']
 28
 29
 30async def send_to_social_media_bridge(client: Client, message: Message, url: str, platform: str, **kwargs):
 31    """See docs in `bridge/README.md` for details."""
 32    if await i_am_bot(client):  # bot can't send message to other bots
 33        return
 34
 35    params = {
 36        "target_cid": kwargs["target_chat"] if kwargs.get("target_chat") else message.chat.id,  # MSG-A's cid
 37        "target_mid": kwargs.get("target_mid"),  # disable reply, because the reply message may be sent as a series of messages.
 38        "url": url,
 39        "caption": kwargs.get("caption"),
 40    }
 41
 42    # add progress message
 43    if (prog := kwargs.get("progress")) and isinstance(prog, Message):
 44        params["prog_cid"] = prog.chat.id
 45        params["prog_mid"] = prog.id
 46
 47    # save a global flag to cache, if any bot is processing this url, set this the the bot name
 48    # ! Warning: currently we can only handle single url at a time
 49    cache.set("social-global", "waiting-for-bots", ttl=120)
 50
 51    for bot_name, platforms in SOCIAL_BOTS.items():
 52        if platform in platforms:
 53            cache.set(f"social-{bot_name}", params, ttl=120)  # save params to cache for each bot
 54            # determine the text to send
 55            text = url
 56            if kwargs.get(f"prefix-{bot_name}"):  # override the prefix
 57                text = f"{kwargs[f'prefix-{bot_name}']}{url}"
 58            with contextlib.suppress(Exception):
 59                logger.warning(f"Trying {platform} bridge (@{bot_name}): {text}")
 60                await client.send_message(chat_id=f"@{bot_name}", text=text)
 61
 62
 63async def forward_social_media_results(client: Client, message: Message):
 64    """See docs in `bridge/README.md` for details.
 65
 66    Note, we may receive a series of messages from different bots
 67    First, we need to check the global flag in the cache. Format: social-global-{url}
 68    Then, we need to check the cache for each bot. Format: social-individual-{bot}-{url}
 69    """
 70    if message.from_user.username not in SOCIAL_BOTS:
 71        return
 72    # check media type
 73    if MEDIA_TYPES.get(message.from_user.username):
 74        info = parse_msg(message, silent=True)
 75        if info["mtype"] not in MEDIA_TYPES[message.from_user.username]:
 76            return
 77    elif not (message.photo or message.video):  # By default, only forward video and photo messages
 78        return
 79
 80    # ! Fix wired bug. When @Music163bot send us an audio file, the message was recieved 2 times.
 81    # ! We only want to process it once
 82    if cache.get(f"social-processed-{message.id}"):
 83        logger.warning("Already processed, skipping")
 84        return
 85    cache.set(f"social-processed-{message.id}", "Done", ttl=120)  # cache id for 2 minutes
 86    #  got a media message
 87    info = parse_msg(message)
 88    bot_name = cache.get("social-global", default="")
 89    if bot_name == "waiting-for-bots":  # this bot is the first one to get the results
 90        logger.success(f"Bridge @{info['handle']} is the first bot to get a {info['mtype']}")
 91        cache.set("social-global", info["handle"], ttl=120)
 92    elif bot_name != info["handle"]:  # already processed by other bot
 93        return
 94    params = cache.get(f"social-{info['handle']}")
 95    if not params:
 96        logger.warning("Already processed, skipping")
 97        return
 98
 99    logger.info(f"Forwarding {info['mtype']} from @{info['handle']} -> chat={params['target_cid']}, id={params['target_mid']}")
100    # determine caption
101    caption = CAPTIONS.get(info["handle"], "")  # set globally for this bot
102    if params.get("caption"):  # set for this message
103        caption = params["caption"]
104    await client.copy_message(
105        chat_id=params["target_cid"],
106        from_chat_id=info["cid"],
107        message_id=info["mid"],
108        caption=caption,
109        reply_parameters=ReplyParameters(message_id=params["target_mid"]),
110    )
111    # ! Because `cache` is not shared between different processes, we can't safely use it to store media_group_id
112    # if not message.media_group_id:  # single media, send diectly
113    #     logger.info(f"Forwarding {info['mtype']} from @{bot_name} -> chat={params['target_cid']}, id={params['target_mid']}")
114    #     await client.copy_message(chat_id=params["target_cid"], from_chat_id=info["cid"], message_id=info["mid"], reply_parameters=ReplyParameters(message_id=params["target_mid"]))
115    # # Media group. We will receive multiple messages at nearly the same time
116    # # Send media_group only once
117    # if not cache.get(f"social-{bot_name}-{message.media_group_id}"):
118    #     cache.set(f"social-{bot_name}-{message.media_group_id}", info["mid"], ttl=120)  # this may be overwritten by the next message of same media_group
119    #     await asyncio.sleep(1 + random.random())  # wait for other messages
120    #     if mid := cache.get(f"social-{bot_name}-{message.media_group_id}"):
121    #         logger.info(f"Forwarding media_group from @{bot_name} -> chat={params['target_cid']}, id={params['target_mid']}")
122    #         await client.copy_media_group(chat_id=params["target_cid"], from_chat_id=info["cid"], message_id=mid, reply_parameters=ReplyParameters(message_id=params["target_mid"]))
123    with contextlib.suppress(Exception):
124        if params.get("prog_cid") and params.get("prog_mid"):
125            await client.delete_messages(chat_id=params["prog_cid"], message_ids=params["prog_mid"])
126        # only handle once
127        if info["handle"] == "Music163bot":
128            cache.delete("social-global")
129            cache.delete("social-Music163bot")