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")