Commit 0c3de72
Changed files (19)
src/ai/texts/claude.py
@@ -9,14 +9,14 @@ from anthropic import AsyncAnthropic, DefaultAioHttpClient
from glom import Coalesce, glom
from loguru import logger
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_DELIM
from pyrogram.types import Message, ReplyParameters
from ai.texts.contexts import get_anthropic_contexts
from ai.utils import BOT_TIPS, EMOJI_REASONING_BEGIN, EMOJI_TEXT_BOT, beautify_llm_response, literal_eval, trim_none
from config import AI, PROXY, TEXT_LENGTH
from messages.progress import modify_progress
-from messages.utils import blockquote, count_without_entities, delete_message, smart_split
+from messages.utils import blockquote, count_without_entities, delete_message, quote, smart_split
from utils import number_to_emoji, rand_string, strings_list
@@ -157,9 +157,9 @@ async def single_api_response(
is_reasoning = False
if response_type == "thinking" and len(thoughts) == 0: # 首次收到推理内容
- runtime_texts += f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}"
+ runtime_texts += quote(f"{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}")
elif chunk_thinking: # 收到推理内容
- runtime_texts += chunk_thinking
+ runtime_texts += chunk_thinking.replace("\n", f"\n{BLOCKQUOTE_DELIM}")
if response_type == "text": # 收到初始回答
runtime_texts = chunk_answer.lstrip()
@@ -179,13 +179,11 @@ async def single_api_response(
if len(parts) == 1:
continue
if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}" # remove previous thinking
+ runtime_texts = quote(f"{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}") # remove previous thinking
await modify_progress(message=status_msg, text=parts[0], force_update=True) # force send the first part
else:
await modify_progress(message=status_msg, text=blockquote(parts[0]), force_update=True) # force send the first part
runtime_texts = parts[-1] # keep the last part
- if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{runtime_texts.lstrip()}"
if not silent:
status_msg = await client.send_message(status_cid, text=prefix + runtime_texts, reply_parameters=ReplyParameters(message_id=status_mid)) # the new message
sent_messages.append(status_msg)
src/ai/texts/gemini.py
@@ -8,14 +8,14 @@ from google import genai
from google.genai import types
from loguru import logger
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_DELIM
from pyrogram.types import Message, ReplyParameters
from ai.texts.contexts import get_gemini_contexts
from ai.utils import BOT_TIPS, EMOJI_REASONING_BEGIN, EMOJI_TEXT_BOT, beautify_llm_response, literal_eval, trim_none
from config import AI, PROXY, TEXT_LENGTH
from messages.progress import modify_progress
-from messages.utils import blockquote, count_without_entities, smart_split
+from messages.utils import blockquote, count_without_entities, quote, smart_split
from networking import flatten_rediercts
from utils import number_to_emoji, strings_list
@@ -55,7 +55,7 @@ async def gemini_chat_completion(
try:
http_options = types.HttpOptions(base_url=gemini_base_url, async_client_args={"proxy": gemini_proxy}, headers=literal_eval(gemini_default_headers))
gemini = genai.Client(api_key=api_key, http_options=http_options)
- params = {"model": model_id, "contents": await get_gemini_contexts(client, message, gemini)}
+ params: dict = {"model": model_id, "contents": await get_gemini_contexts(client, message, gemini)}
if conf := literal_eval(gemini_generate_content_config):
params["config"] = conf
logger.debug(f"genai.Client().models.generate_content_stream(**{params})")
@@ -114,8 +114,8 @@ async def single_api_generate_content(
sent_messages = []
resp = {}
try:
- is_reasoning = False
- reasoning_chat_flag = None # 用于指示是否是推理对话
+ reasoning_chat_flag = None # 是否是推理模型
+ is_reasoning = False # 是否正在推理
async for chunk in await gemini.aio.models.generate_content_stream(**params):
resp = parse_chunk(chunk)
chunk_answer = resp.get("texts", "")
@@ -124,9 +124,9 @@ async def single_api_generate_content(
reasoning_chat_flag = True
if chunk_thinking and not is_reasoning: # 首次收到推理内容
is_reasoning = True
- runtime_texts += f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}"
+ runtime_texts += quote(f"{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}")
elif chunk_thinking and is_reasoning: # 收到推理内容且正在思考
- runtime_texts += chunk_thinking
+ runtime_texts += chunk_thinking.replace("\n", f"\n{BLOCKQUOTE_DELIM}")
elif reasoning_chat_flag is True and is_reasoning: # Receiving response, close reasoning flag
is_reasoning = False
runtime_texts = chunk_answer.lstrip()
@@ -143,13 +143,11 @@ async def single_api_generate_content(
if len(parts) == 1:
continue
if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}" # remove previous thinking
+ runtime_texts = quote(f"{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}") # remove previous thinking
await modify_progress(message=status_msg, text=parts[0], force_update=True) # force send the first part
else:
await modify_progress(message=status_msg, text=blockquote(parts[0]), force_update=True) # force send the first part
runtime_texts = parts[-1] # keep the last part
- if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{runtime_texts.lstrip()}"
if not silent:
status_msg = await client.send_message(status_cid, text=prefix + runtime_texts, reply_parameters=ReplyParameters(message_id=status_mid)) # the new message
sent_messages.append(status_msg)
src/ai/texts/openai_chat.py
@@ -1,20 +1,19 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import contextlib
-import re
from glom import glom
from loguru import logger
from openai import AsyncOpenAI, DefaultAsyncHttpxClient
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_DELIM
from pyrogram.types import Message, ReplyParameters
from ai.texts.contexts import get_openai_completion_contexts
-from ai.utils import BOT_TIPS, EMOJI_REASONING_BEGIN, EMOJI_REASONING_END, EMOJI_TEXT_BOT, beautify_llm_response, literal_eval, split_reasoning, trim_none
+from ai.utils import BOT_TIPS, EMOJI_REASONING_BEGIN, EMOJI_TEXT_BOT, beautify_llm_response, literal_eval, split_reasoning, trim_none
from config import AI, PROXY, TEXT_LENGTH
from messages.progress import modify_progress
-from messages.utils import blockquote, count_without_entities, delete_message, smart_split
+from messages.utils import blockquote, count_without_entities, delete_message, quote, smart_split
from utils import strings_list
@@ -133,8 +132,8 @@ async def single_api_chat_completions(
sent_messages = []
resp = ""
try:
- is_reasoning = False
- reasoning_chat_flag = None # 用于指示是否是推理对话
+ reasoning_chat_flag = None # 是否是推理模型
+ is_reasoning = False # 是否正在推理
async for chunk in await openai.chat.completions.create(**params):
resp = trim_none(chunk.model_dump())
logger.trace(resp)
@@ -148,24 +147,15 @@ async def single_api_chat_completions(
reasoning_chat_flag = True
if chunk_thinking and not is_reasoning: # 首次收到推理内容
is_reasoning = True
- runtime_texts += f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}"
+ runtime_texts += quote(f"{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}")
elif chunk_thinking and is_reasoning: # 收到推理内容且正在思考
- runtime_texts += chunk_thinking
+ runtime_texts += chunk_thinking.replace("\n", f"\n{BLOCKQUOTE_DELIM}")
elif reasoning_chat_flag is True and is_reasoning: # 收到回答, 关闭推理标志
is_reasoning = False
runtime_texts = chunk_answer.lstrip()
else:
runtime_texts += chunk_answer
- # Sometimes the reasoning content is included in the content field.
- # handle "<think>...</think>\n\n"
- if runtime_texts.removeprefix(prefix).lstrip().startswith("<think>"):
- is_reasoning = True
- runtime_texts = runtime_texts.replace("<think>", f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}")
- if "</think>" in runtime_texts:
- is_reasoning = False
- runtime_texts = re.sub(r"</think>\s*", f"{EMOJI_REASONING_END}\n{BLOCKQUOTE_EXPANDABLE_END_DELIM}", runtime_texts, count=1)
-
thoughts += chunk_thinking
answers += chunk_answer
runtime_texts = beautify_llm_response(runtime_texts)
@@ -178,13 +168,11 @@ async def single_api_chat_completions(
if len(parts) == 1:
continue
if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}" # remove previous thinking
+ runtime_texts = quote(f"{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}") # remove previous thinking
await modify_progress(message=status_msg, text=parts[0], force_update=True) # force send the first part
else:
await modify_progress(message=status_msg, text=blockquote(parts[0]), force_update=True) # force send the first part
runtime_texts = parts[-1] # keep the last part
- if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{runtime_texts.lstrip()}"
if not silent:
status_msg = await client.send_message(status_cid, text=prefix + runtime_texts, reply_parameters=ReplyParameters(message_id=status_mid)) # the new message
sent_messages.append(status_msg)
src/ai/texts/openai_response.py
@@ -8,7 +8,7 @@ from glom import Coalesce, glom
from loguru import logger
from openai import AsyncOpenAI, DefaultAsyncHttpxClient
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_DELIM
from pyrogram.types import Message, ReplyParameters
from ai.texts.contexts import get_openai_response_contexts
@@ -17,7 +17,7 @@ from config import AI, PROXY, TEXT_LENGTH
from database.r2 import set_cf_r2
from messages.parser import get_thread_id
from messages.progress import modify_progress
-from messages.utils import blockquote, count_without_entities, delete_message, smart_split
+from messages.utils import blockquote, count_without_entities, delete_message, quote, smart_split
from utils import number_to_emoji, strings_list
@@ -203,10 +203,10 @@ async def single_api_response(
}: # 推理结束
is_reasoning = False
- if response_type == "response.reasoning_summary_part.added": # 首次收到推理内容
- runtime_texts += f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}"
+ if response_type == "response.reasoning_summary_part.added" and len(thoughts) == 0: # 首次收到推理内容
+ runtime_texts += quote(f"{EMOJI_REASONING_BEGIN}{chunk_thinking.lstrip()}")
elif chunk_thinking: # 收到推理内容
- runtime_texts += chunk_thinking
+ runtime_texts += chunk_thinking.replace("\n", f"\n{BLOCKQUOTE_DELIM}")
if response_type == "response.content_part.added": # 收到初始回答
runtime_texts = chunk_answer.lstrip()
@@ -223,13 +223,11 @@ async def single_api_response(
if len(parts) == 1:
continue
if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}" # remove previous thinking
+ runtime_texts = quote(f"{EMOJI_REASONING_BEGIN}{parts[-1].lstrip()}") # remove previous thinking
await modify_progress(message=status_msg, text=parts[0], force_update=True) # force send the first part
else:
await modify_progress(message=status_msg, text=blockquote(parts[0]), force_update=True) # force send the first part
runtime_texts = parts[-1] # keep the last part
- if is_reasoning:
- runtime_texts = f"{BLOCKQUOTE_EXPANDABLE_DELIM}{EMOJI_REASONING_BEGIN}{runtime_texts.lstrip()}"
if not silent:
status_msg = await client.send_message(status_cid, text=prefix + runtime_texts, reply_parameters=ReplyParameters(message_id=status_mid)) # the new message
sent_messages.append(status_msg)
src/ai/utils.py
@@ -12,7 +12,7 @@ from glom import glom
from google import genai
from google.genai.types import HttpOptions
from loguru import logger
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
from config import AI, PREFIX, PROXY
from database.kv import get_cf_kv
@@ -80,8 +80,7 @@ def clean_bot_tips(text: str) -> str:
def clean_reasoning(text: str) -> str:
text = re.sub(rf"{EMOJI_REASONING_BEGIN}(.*?){EMOJI_REASONING_END}", "", text.strip(), flags=re.DOTALL).strip()
- text = text.removeprefix(BLOCKQUOTE_EXPANDABLE_DELIM).lstrip()
- return text.removeprefix(BLOCKQUOTE_EXPANDABLE_END_DELIM).lstrip()
+ return text.replace(BLOCKQUOTE_EXPANDABLE_DELIM, "").strip()
def clean_context(text: str) -> str:
src/history/query.py
@@ -6,7 +6,7 @@ from io import BytesIO
from glom import glom
from loguru import logger
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
from pyrogram.types import Message, User
from config import HISTORY, PREFIX, TZ, cache
@@ -32,15 +32,14 @@ HELP = f"""🗣**查询当前对话聊天记录**
4.`/hist + 日期 + @用户名 + 关键词` (日期需放在最前面)
示例:
{BLOCKQUOTE_EXPANDABLE_DELIM}`/hist 你好`: 查询包含“你好”关键词的记录
-`/hist 2025-01-01 你好`: 查询2025-01-01日包含“你好”的记录
-`/hist @张三 你好`: 查询用户【张三】包含“你好”的记录
-`/hist 2025 @张三 你好`: 查询2025年用户【张三】包含“你好”的记录
-
-注意:
-- 用户名和关键词需要区分大小写
-- 用户名可以为昵称 (Name)、用户名 (@username)、用户的TelegramUID
-- 如果用户名中有空格, 请去除空格。例如: 想指定用户为John Doe请使用 `@JohnDoe`
-{BLOCKQUOTE_EXPANDABLE_END_DELIM}
+{BLOCKQUOTE_EXPANDABLE_DELIM}`/hist 2025-01-01 你好`: 查询2025-01-01日包含“你好”的记录
+{BLOCKQUOTE_EXPANDABLE_DELIM}`/hist @张三 你好`: 查询用户【张三】包含“你好”的记录
+{BLOCKQUOTE_EXPANDABLE_DELIM}`/hist 2025 @张三 你好`: 查询2025年用户【张三】包含“你好”的记录
+{BLOCKQUOTE_EXPANDABLE_DELIM}
+{BLOCKQUOTE_EXPANDABLE_DELIM}注意:
+{BLOCKQUOTE_EXPANDABLE_DELIM}- 用户名和关键词需要区分大小写
+{BLOCKQUOTE_EXPANDABLE_DELIM}- 用户名可以为昵称 (Name)、用户名 (@username)、用户的TelegramUID
+{BLOCKQUOTE_EXPANDABLE_DELIM}- 如果用户名中有空格, 请去除空格。例如: 想指定用户为John Doe请使用 `@JohnDoe`
`/history` 使用说明:
查询所有对话的聊天记录
但出于隐私考虑, 本命令会限制使用权限
src/messages/sender.py
@@ -7,13 +7,12 @@ from pathlib import Path
from loguru import logger
from pyrogram.client import Client
from pyrogram.errors import FloodWait
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
from pyrogram.types import Message, ReplyParameters
from config import CAPTION_LENGTH, TID
from messages.preprocess import preprocess_media, warp_media_group
from messages.progress import modify_progress, telegram_uploading
-from messages.utils import delete_message, get_reply_to, smart_split, summay_media, warp_comments
+from messages.utils import delete_message, get_reply_to, smart_split, summay_media
from utils import to_int
@@ -73,7 +72,7 @@ async def send2tg(
media = await preprocess_media(media)
texts = texts or ""
if comments: # append comments to texts
- texts = texts + "".join(comments) + BLOCKQUOTE_EXPANDABLE_END_DELIM
+ texts = texts + "".join(comments)
if send_from_user: # prefix send_from_user
texts = f"{send_from_user}{texts.strip()}"
@@ -88,7 +87,6 @@ async def send2tg(
caption = (await smart_split(texts, CAPTION_LENGTH))[0]
remaining_texts = texts.removeprefix(caption)
- caption = warp_comments(caption)
if 1 < len(media) <= 10:
group = await warp_media_group(media, caption=caption, caption_above=caption_above)
sent_messages.extend(await send_media_group(client, target_chat, group, reply_parameters))
@@ -130,14 +128,8 @@ async def send_texts(
for idx, msg in enumerate(await smart_split(texts.strip())):
if not msg:
continue
- texts = warp_comments(msg)
- # we do not send comments only texts
- if (
- texts.startswith(BLOCKQUOTE_EXPANDABLE_DELIM)
- and texts.endswith(BLOCKQUOTE_EXPANDABLE_END_DELIM)
- and texts.count(BLOCKQUOTE_EXPANDABLE_DELIM) == 1
- and texts.count(BLOCKQUOTE_EXPANDABLE_END_DELIM) == 1
- ):
+ # we do not send comments-only texts
+ if all(s.startswith("**>") for s in msg.split("\n") if s):
continue
if idx != 0:
reply_parameters = ReplyParameters()
@@ -168,7 +160,6 @@ async def send_single_media(
logger.trace(f"Sending single media with {len(texts)} texts")
caption = (await smart_split(texts, CAPTION_LENGTH))[0]
remaining_texts = texts.removeprefix(caption)
- caption = warp_comments(caption)
message = None
try:
if media.get("photo"):
src/messages/utils.py
@@ -6,7 +6,7 @@ import re
from loguru import logger
from pyrogram.client import Client
from pyrogram.enums import ParseMode
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_DELIM, BLOCKQUOTE_EXPANDABLE_DELIM
from pyrogram.parser.parser import Parser
from pyrogram.types import Message, ReactionTypeEmoji, ReplyParameters
@@ -159,8 +159,7 @@ async def smart_split(text: str, chars_per_string: int = TEXT_LENGTH, mode: Pars
return strings[: matched.end()]
return strings
- # for some reason, we may need to prepend `BLOCKQUOTE_EXPANDABLE_DELIM` or append `BLOCKQUOTE_EXPANDABLE_END_DELIM`
- chars_per_string = chars_per_string - len(BLOCKQUOTE_EXPANDABLE_DELIM) - len(BLOCKQUOTE_EXPANDABLE_END_DELIM)
+ chars_per_string = chars_per_string - len(BLOCKQUOTE_EXPANDABLE_DELIM) * text.count(BLOCKQUOTE_EXPANDABLE_DELIM)
parts = []
while True:
if await count_without_entities(text, mode) < chars_per_string:
@@ -182,34 +181,14 @@ async def smart_split(text: str, chars_per_string: int = TEXT_LENGTH, mode: Pars
def blockquote(s: str) -> str:
"""Block quote texts."""
- s = s.replace(BLOCKQUOTE_EXPANDABLE_DELIM, "").replace(BLOCKQUOTE_EXPANDABLE_END_DELIM, "")
- return BLOCKQUOTE_EXPANDABLE_DELIM + s + "\n" + BLOCKQUOTE_EXPANDABLE_END_DELIM
+ s = s.replace(BLOCKQUOTE_EXPANDABLE_DELIM, "")
+ return BLOCKQUOTE_EXPANDABLE_DELIM + s.replace("\n", f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}")
-def warp_comments(texts: str) -> str:
- texts = texts or ""
-
- start_idx = texts.find(BLOCKQUOTE_EXPANDABLE_DELIM)
- end_idx = texts.find(BLOCKQUOTE_EXPANDABLE_END_DELIM)
- if start_idx == -1 and end_idx == -1: # no comments
- return texts
-
- # <comments><end_delim> ... <content> ... <start_delim><comments>
- if start_idx > -1 and end_idx > -1 and end_idx < start_idx:
- texts = texts.removeprefix(BLOCKQUOTE_EXPANDABLE_DELIM).removesuffix(BLOCKQUOTE_EXPANDABLE_END_DELIM)
- return blockquote(texts)
-
- # <content> ... <start_delim><comments>
- if start_idx > -1 and end_idx == -1:
- texts = texts.removesuffix(BLOCKQUOTE_EXPANDABLE_END_DELIM)
- return texts + BLOCKQUOTE_EXPANDABLE_END_DELIM
-
- # <comments><end_delim> ... <content>
- if start_idx == -1 and end_idx > -1:
- texts = texts.removeprefix(BLOCKQUOTE_EXPANDABLE_DELIM)
- return BLOCKQUOTE_EXPANDABLE_DELIM + texts
-
- return texts
+def quote(s: str) -> str:
+ """Quote texts."""
+ s = s.removeprefix(BLOCKQUOTE_DELIM)
+ return BLOCKQUOTE_DELIM + s.replace("\n", f"\n{BLOCKQUOTE_DELIM}")
async def sent_from_me(client: Client, message: Message) -> bool:
src/preview/bilibili.py
@@ -82,7 +82,7 @@ async def preview_bilibili(
@cache.memoize(ttl=30)
-async def parse_bilibili_opus(post_id: str, **kwargs) -> dict: # type: ignore
+async def parse_bilibili_opus(post_id: str, **kwargs) -> dict:
try:
op = opus.Opus(int(post_id))
resp = await op.get_info()
@@ -266,7 +266,7 @@ async def get_bilibili_comments(url_or_vid: int | str) -> list[str]:
if cmt := glom(x, "content.message", default=""):
if idx == 0:
comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:")
- comments.append(f"\n💬**{name}**{location}: {emojify(cmt)}")
+ comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{name}**{location}: {emojify(cmt)}")
except Exception as e:
logger.error(f"Failed to get Bilibili comments: {e}")
return []
src/preview/douyin.py
@@ -93,7 +93,7 @@ async def preview_douyin(
comments = []
if comments_list := await get_comments(data["aweme_id"], platform, douyin_comments_provider):
comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:")
- comments.extend(f"\n💬**{cmt['name']}**{cmt['region']}: {cmt['text']}" for cmt in comments_list)
+ comments.extend(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{cmt['name']}**{cmt['region']}: {cmt['text']}" for cmt in comments_list)
sent_messages = await send2tg(client, message, texts=emojify(texts), media=data.get("media", []), comments=comments, **kwargs)
await modify_progress(del_status=True, **kwargs)
src/preview/instagram.py
@@ -110,7 +110,7 @@ async def preview_instagram(
comment_list = [{"author": glom(node, "node.owner.username", default="user"), "text": glom(node, "node.text", default="")} for node in comment_nodes]
if comment_list := [x for x in comment_list if x["text"]]:
comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:")
- comments.extend(f"\n💬**[{cmt['author']}](https://www.instagram.com/{cmt['author']})**: {cmt['text']}" for cmt in comment_list)
+ comments.extend(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**[{cmt['author']}](https://www.instagram.com/{cmt['author']})**: {cmt['text']}" for cmt in comment_list)
await modify_progress(text=f"⏬正在下载:\n{summay_media(media)}", force_update=True, **kwargs)
media = await download_media(media, **kwargs)
@@ -151,11 +151,11 @@ async def preview_ddinstagram(client: Client, message: Message, url: str, post_t
texts = ""
media = {}
if tag := soup.find("meta", attrs={"property": "twitter:title"}):
- author = tag.get("content", "Unknown") # type: ignore
+ author = tag.get("content", "Unknown")
texts += f"🏞**[{author}]({url})\n"
if tag := soup.find("meta", attrs={"property": "og:description"}):
- texts += tag.get("content", "") # type: ignore
- if (tag := soup.find("meta", attrs={"property": "twitter:image"})) and (img_url := tag.get("content")): # type: ignore
+ texts += str(tag.get("content", ""))
+ if (tag := soup.find("meta", attrs={"property": "twitter:image"})) and (img_url := tag.get("content")):
raw_url = f"{API.DDINSTAGRAM}{img_url}"
media["photo"] = await download_file(raw_url, path=f"{DOWNLOAD_DIR}/{post_id}.jpg", proxy=PROXY.INSTAGRAM, **kwargs)
if not bool(validate_img(media["photo"])):
@@ -163,7 +163,7 @@ async def preview_ddinstagram(client: Client, message: Message, url: str, post_t
return
if tag := soup.find("meta", attrs={"property": "og:video"}):
- video_url = tag.get("content", "") # type: ignore
+ video_url = tag.get("content", "")
if video_url:
raw_url = f"{API.DDINSTAGRAM}{video_url}"
media["video"] = await download_file(raw_url, path=f"{DOWNLOAD_DIR}/{post_id}.mp4", proxy=PROXY.INSTAGRAM, **kwargs)
src/preview/reddit.py
@@ -86,7 +86,7 @@ async def get_reddit_info(url: str, **kwargs) -> dict:
continue
if comment == "[removed]" or has_markdown_img(comment):
continue
- comments.append(f"\n💬**[{author}]({author_url})**: {comment}")
+ comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**[{author}]({author_url})**: {comment}")
if comments:
comments.insert(0, f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:")
await modify_progress(text=f"⏬正在下载:\n{summay_media(media)}", force_update=True, **kwargs)
src/preview/twitter.py
@@ -8,7 +8,7 @@ from zoneinfo import ZoneInfo
from glom import glom
from loguru import logger
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
+from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM
from pyrogram.types import Message
from bridge.social import send_to_social_media_bridge
@@ -169,8 +169,7 @@ async def preview_twitter(
for cmt in comments:
if str(cmt["post_id"]) == str(this_info["post_id"]):
continue
- msg += f"\n💬**{cmt['author']}**: {cmt['text']}"
- msg += BLOCKQUOTE_EXPANDABLE_END_DELIM
+ msg += f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{cmt['author']}**: {cmt['text']}"
media.extend(master_media)
# 本条推文
@@ -197,8 +196,7 @@ async def preview_twitter(
msg += f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:"
for cmt in comments:
cmt_texts = cmt["text"].strip().removeprefix(f"@{master_handle}").strip() # 有时回推的comment前会附带被回推的handle, 这里去掉
- msg += f"\n💬**{cmt['author']}**: {cmt_texts}"
- msg += BLOCKQUOTE_EXPANDABLE_END_DELIM
+ msg += f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{cmt['author']}**: {cmt_texts}"
# 引用推文
if quote_info:
src/preview/wechat.py
@@ -6,7 +6,6 @@ from urllib.parse import quote_plus
from loguru import logger
from pyrogram.client import Client
-from pyrogram.parser.markdown import BLOCKQUOTE_EXPANDABLE_DELIM, BLOCKQUOTE_EXPANDABLE_END_DELIM
from pyrogram.types import Message
from config import API, CAPTION_LENGTH, DB, DOWNLOAD_DIR, PROXY, TEXT_LENGTH, TOKEN
@@ -14,7 +13,7 @@ from database.database import get_db
from messages.database import copy_messages_from_db, save_messages
from messages.progress import modify_progress
from messages.sender import send2tg
-from messages.utils import count_without_entities, summay_media
+from messages.utils import blockquote, count_without_entities, summay_media
from networking import download_file, download_media, hx_req
from publish import publish_telegraph
from utils import nowstr, rand_string
@@ -47,7 +46,7 @@ async def preview_wechat(client: Client, message: Message, url: str = "", db_key
length = await count_without_entities(post_info["header"] + post_info["markdown"])
if not post_info.get("media"): # 无图片
if length < TEXT_LENGTH - 8: # 无图片短文
- texts = f"{post_info['header']}\n{BLOCKQUOTE_EXPANDABLE_DELIM}{post_info['markdown']}\n{BLOCKQUOTE_EXPANDABLE_END_DELIM}"
+ texts = f"{post_info['header']}\n{blockquote(post_info['markdown'])}"
sent_messages.extend(await send2tg(client, message, texts=texts, **kwargs))
else: # 无图片长文
texts = f"{post_info['header']}"
@@ -56,7 +55,7 @@ async def preview_wechat(client: Client, message: Message, url: str = "", db_key
texts += f"\n⚡️[即时预览]({telegraph_url})"
sent_messages.extend(await send2tg(client, message, texts=texts, media=[{"document": post_info["html_path"]}], **kwargs))
elif length < CAPTION_LENGTH - 8: # 有图片短文
- texts = f"{post_info['header']}\n{BLOCKQUOTE_EXPANDABLE_DELIM}{post_info['markdown']}\n{BLOCKQUOTE_EXPANDABLE_END_DELIM}"
+ texts = f"{post_info['header']}\n{blockquote(post_info['markdown'])}"
sent_messages.extend(await send2tg(client, message, texts=texts, media=post_info["media"], **kwargs))
else: # 有图片长文
texts = f"{post_info['header']}"
src/preview/weibo.py
@@ -145,7 +145,7 @@ async def preview_weibo(
@cache.memoize(ttl=30)
-async def parse_weibo_info(post_id: str, data: dict | None = None, **kwargs) -> dict: # type: ignore
+async def parse_weibo_info(post_id: str, data: dict | None = None, **kwargs) -> dict:
info = {}
if not data:
weibo_url = f"https://m.weibo.cn/detail/{post_id}"
@@ -292,9 +292,9 @@ async def parse_weibo_comments(post_id: str) -> list[str]:
uid = glom(info, "user.id", default="")
author = glom(info, "user.screen_name", default="")
if author and uid:
- cmt += f"💬**[{author}](https://weibo.com/u/{uid})**"
+ cmt += f"{BLOCKQUOTE_EXPANDABLE_DELIM}💬**[{author}](https://weibo.com/u/{uid})**"
elif author:
- cmt += f"💬**{author}**"
+ cmt += f"{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{author}**"
if region := info.get("source", "").removeprefix("来自"):
cmt += f"({region})"
cmt += ":"
src/preview/youtube.py
@@ -40,7 +40,7 @@ async def get_youtube_comments(vid: str | None) -> list[str]:
if cmt := glom(x, "snippet.topLevelComment.snippet.textDisplay", default=""):
if idx == 0:
comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**点此展开评论区**:")
- comments.append(f"\n💬**{name}**: {cmt}")
+ comments.append(f"\n{BLOCKQUOTE_EXPANDABLE_DELIM}💬**{name}**: {cmt}")
except Exception as e:
logger.error(f"Failed to get YouTube comments: {e}")
return []
@@ -76,7 +76,7 @@ async def get_youtube_vinfo(video_id: str) -> dict:
"""
if not video_id:
return {"downloadable": False, "error_msg": "❌未提供VideoID"}
- info = {"downloadable": False, "error_msg": "❌无法获取此视频信息"}
+ info: dict = {"downloadable": False, "error_msg": "❌无法获取此视频信息"}
try:
logger.info(f"Fetch YouTube video info for {video_id=}, proxy={PROXY.GOOGLE}")
api = "https://www.googleapis.com/youtube/v3/videos"
src/ytdlp/main.py
@@ -20,7 +20,7 @@ from messages.database import copy_messages_from_db, save_messages
from messages.preprocess import preprocess_media
from messages.progress import modify_progress, telegram_uploading
from messages.sender import send2tg
-from messages.utils import count_without_entities, get_reply_to, smart_split, warp_comments
+from messages.utils import count_without_entities, get_reply_to, smart_split
from multimedia import convert_to_h264
from preview.bilibili import get_bilibili_comments, get_bilibili_vinfo, make_bvid_clickable
from preview.youtube import get_youtube_comments, get_youtube_vinfo
@@ -338,7 +338,7 @@ async def send_media(
video_messages.append(
await client.send_video(
chat_id=to_int(video_target),
- caption=warp_comments(caption),
+ caption=caption,
reply_parameters=reply_parameters,
progress=telegram_uploading,
progress_args=(kwargs.get("progress", False), video["video"], true(kwargs.get("detail_progress"))), # message, path, detail_progress
@@ -352,7 +352,7 @@ async def send_media(
audio_message = await client.send_audio(
chat_id=to_int(audio_target),
audio=audio_path.as_posix(),
- caption=warp_comments(caption),
+ caption=caption,
performer=info["author"],
title=info["title"],
duration=round(float(info.get("duration", "0"))),
pyproject.toml
@@ -1,20 +1,20 @@
[project]
dependencies = [
"aioboto3==15.5.0",
- "anthropic==0.84.0",
+ "anthropic==0.86.0",
"apscheduler>=3.11.0,<4.0.0",
"beautifulsoup4==4.14.3",
"bilibili-api-python==17.4.1",
"brotli==1.2.0",
"cacheout==0.16.0",
- "chardet==6.0.0.post1",
- "curl-cffi==0.15.0b4",
+ "chardet==7.4.0.post2",
+ "curl-cffi==0.15.0rc1",
"cutword==0.1.1",
- "dashscope==1.25.12",
+ "dashscope==1.25.15",
"feedgen==1.0.0",
"feedparser==6.0.12",
"glom==25.12.0",
- "google-genai==1.65.0",
+ "google-genai==1.69.0",
"httpx-aiohttp==0.1.12",
"httpx-curl-cffi==0.1.5",
"httpx[http2,socks]==0.28.1",
@@ -22,13 +22,13 @@ dependencies = [
"markdown==3.10.2",
"markitdown[docx,pdf,pptx,xls,xlsx]==0.1.5",
"onnxruntime>=1.23.2",
- "openai==2.24.0",
+ "openai==2.30.0",
"orjson==3.11.7",
"pathvalidate==3.3.1",
"pillow-heif==1.3.0",
"pillow>=11.2.1",
- "puremagic==2.0.0",
- "pyrotgfork==2.2.19",
+ "puremagic==2.1.1",
+ "pyrotgfork==2.2.21",
"pysocks==1.7.1",
"pytgcrypto>=1.2.12",
"python-ffmpeg",
uv.lock
@@ -138,7 +138,7 @@ wheels = [
[[package]]
name = "anthropic"
-version = "0.84.0"
+version = "0.86.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -150,9 +150,9 @@ dependencies = [
{ name = "sniffio", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/04/ea/0869d6df9ef83dcf393aeefc12dd81677d091c6ffc86f783e51cf44062f2/anthropic-0.84.0.tar.gz", hash = "sha256:72f5f90e5aebe62dca316cb013629cfa24996b0f5a4593b8c3d712bc03c43c37", size = 539457, upload-time = "2026-02-25T05:22:38.54Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820, upload-time = "2026-03-18T18:43:08.017Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/64/ca/218fa25002a332c0aa149ba18ffc0543175998b1f65de63f6d106689a345/anthropic-0.84.0-py3-none-any.whl", hash = "sha256:861c4c50f91ca45f942e091d83b60530ad6d4f98733bfe648065364da05d29e7", size = 455156, upload-time = "2026-02-25T05:22:40.468Z" },
+ { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400, upload-time = "2026-03-18T18:43:06.526Z" },
]
[[package]]
@@ -269,20 +269,20 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "aioboto3", specifier = "==15.5.0" },
- { name = "anthropic", specifier = "==0.84.0" },
+ { name = "anthropic", specifier = "==0.86.0" },
{ name = "apscheduler", specifier = ">=3.11.0,<4.0.0" },
{ name = "beautifulsoup4", specifier = "==4.14.3" },
{ name = "bilibili-api-python", specifier = "==17.4.1" },
{ name = "brotli", specifier = "==1.2.0" },
{ name = "cacheout", specifier = "==0.16.0" },
- { name = "chardet", specifier = "==6.0.0.post1" },
- { name = "curl-cffi", specifier = "==0.15.0b4" },
+ { name = "chardet", specifier = "==7.4.0.post2" },
+ { name = "curl-cffi", specifier = "==0.15.0rc1" },
{ name = "cutword", specifier = "==0.1.1" },
- { name = "dashscope", specifier = "==1.25.12" },
+ { name = "dashscope", specifier = "==1.25.15" },
{ name = "feedgen", specifier = "==1.0.0" },
{ name = "feedparser", specifier = "==6.0.12" },
{ name = "glom", specifier = "==25.12.0" },
- { name = "google-genai", specifier = "==1.65.0" },
+ { name = "google-genai", specifier = "==1.69.0" },
{ name = "httpx", extras = ["http2", "socks"], specifier = "==0.28.1" },
{ name = "httpx-aiohttp", specifier = "==0.1.12" },
{ name = "httpx-curl-cffi", specifier = "==0.1.5" },
@@ -290,13 +290,13 @@ requires-dist = [
{ name = "markdown", specifier = "==3.10.2" },
{ name = "markitdown", extras = ["docx", "pdf", "pptx", "xls", "xlsx"], specifier = "==0.1.5" },
{ name = "onnxruntime", specifier = ">=1.23.2" },
- { name = "openai", specifier = "==2.24.0" },
+ { name = "openai", specifier = "==2.30.0" },
{ name = "orjson", specifier = "==3.11.7" },
{ name = "pathvalidate", specifier = "==3.3.1" },
{ name = "pillow", specifier = ">=11.2.1" },
{ name = "pillow-heif", specifier = "==1.3.0" },
- { name = "puremagic", specifier = "==2.0.0" },
- { name = "pyrotgfork", specifier = "==2.2.19" },
+ { name = "puremagic", specifier = "==2.1.1" },
+ { name = "pyrotgfork", specifier = "==2.2.21" },
{ name = "pysocks", specifier = "==1.7.1" },
{ name = "pytgcrypto", specifier = ">=1.2.12" },
{ name = "python-ffmpeg", url = "https://github.com/chadawagner/python-ffmpeg/archive/4614d8b7939679ea4d6ae9c32241d7607e2b136c.zip" },
@@ -450,11 +450,17 @@ wheels = [
[[package]]
name = "chardet"
-version = "6.0.0.post1"
+version = "7.4.0.post2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7f/42/fb9436c103a881a377e34b9f58d77b5f503461c702ff654ebe86151bcfe9/chardet-6.0.0.post1.tar.gz", hash = "sha256:6b78048c3c97c7b2ed1fbad7a18f76f5a6547f7d34dbab536cc13887c9a92fa4", size = 12521798, upload-time = "2026-02-22T15:09:17.925Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/03/4b/1fe1ade6b4d33abff0224b45a8310775b04308668ad1bdef725af8e3fcaa/chardet-7.4.0.post2.tar.gz", hash = "sha256:21a6b5ca695252c03385dcfcc8b55c27907f1fe80838aa171b1ff4e356a1bb67", size = 767694, upload-time = "2026-03-29T18:07:23.19Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/66/42/5de54f632c2de53cd3415b3703383d5fff43a94cbc0567ef362515261a21/chardet-6.0.0.post1-py3-none-any.whl", hash = "sha256:c894a36800549adf7bb5f2af47033281b75fdfcd2aa0f0243be0ad22a52e2dcb", size = 627245, upload-time = "2026-02-22T15:09:15.876Z" },
+ { url = "https://files.pythonhosted.org/packages/64/6f/40998484582edf32ebcbe30a51c0b33fb476aa4d22b172d4aabc3f47c5ed/chardet-7.4.0.post2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bdb9387e692dd53c837aa922f676e5ab51209895cd99b15d30c6004418e0d27", size = 854448, upload-time = "2026-03-29T18:07:02.432Z" },
+ { url = "https://files.pythonhosted.org/packages/32/ed/0fc7f4be6d346049bafec134cb4d122317e8e803b42e520f8214f02d9d13/chardet-7.4.0.post2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:422ac637f5a2a8b13151245591cb0fabdf9ec1427725f0560628cb5ad4fb1462", size = 838289, upload-time = "2026-03-29T18:07:04.026Z" },
+ { url = "https://files.pythonhosted.org/packages/27/ff/0f582b7a9369bba8abb47d72c3d1d1122c351b8fb04dcac2637683072bcb/chardet-7.4.0.post2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccdfb13b4a727d3d944157c7f350c6d64630511a0ce39e37ffa5114e90f7d3a7", size = 868537, upload-time = "2026-03-29T18:07:07.093Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/1e/8b5d54ecc873e828e9b91cddfce6bf5a058d7bb3d64007cfbbbc872b0bda/chardet-7.4.0.post2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5862b17677f7e8fcee4e37fe641f01d30762e4b075ac37ce9584e4407896e2d9", size = 853887, upload-time = "2026-03-29T18:07:12.156Z" },
+ { url = "https://files.pythonhosted.org/packages/26/17/8c2cf762c876b04036e561d2a27df8a6305435db1cb584f71c356e319c40/chardet-7.4.0.post2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:22d05c4b7e721d5330d99ef4a6f6233a9de58ae6f2275c21a098bedd778a6cb7", size = 838555, upload-time = "2026-03-29T18:07:13.689Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/b6/13cc503f45beeb1117fc9c83f294df16ebce5d75eac9f0cefb8cce4357a1/chardet-7.4.0.post2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2adfa7390e69cb5ed499b54978d31f6d476788d07d83da3426811181b7ca7682", size = 868868, upload-time = "2026-03-29T18:07:16.781Z" },
+ { url = "https://files.pythonhosted.org/packages/94/d2/22ac0b5b832bb9d2f29311dcded6c09ad0c32c23e3e53a8033aad5eb8652/chardet-7.4.0.post2-py3-none-any.whl", hash = "sha256:e0c9c6b5c296c0e5197bc8876fcc04d58a6ddfba18399e598ba353aba28b038e", size = 625322, upload-time = "2026-03-29T18:07:21.81Z" },
]
[[package]]
@@ -542,22 +548,23 @@ wheels = [
[[package]]
name = "curl-cffi"
-version = "0.15.0b4"
+version = "0.15.0rc1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "cffi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+ { name = "rich", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/18/38/e9cf1387d29345a9121ac0301ff5562111e73024bba88e05f40988da6c4c/curl_cffi-0.15.0b4.tar.gz", hash = "sha256:562d9a94924e822302304fd9c171a8b49f095ce953a6b77ecc79a11f135a4d4c", size = 182953, upload-time = "2026-02-26T04:13:10.023Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/a8/5291c86d73dfeb198c32c1f538d855bb775f8315c9af2760ff529b62ebdd/curl_cffi-0.15.0rc1.tar.gz", hash = "sha256:f7ea46aafec4f245eec3971e92bfb1e838ba5594d7bc004077445001a5f210f6", size = 196386, upload-time = "2026-03-30T10:58:26.254Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a6/ad/d8ee88cc6f71acbb8dcd9b1be06302cb5d31dbea2ca09bb1ee05c001787b/curl_cffi-0.15.0b4-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed794c28497a60ba369895d9fc3f3719425bb489b04eae8ab4a228e4cf3d7568", size = 2784114, upload-time = "2026-02-26T04:12:36.563Z" },
- { url = "https://files.pythonhosted.org/packages/af/ec/009a8f49241a77740840213360cf40a92ccf993134466a17da797c2fd76b/curl_cffi-0.15.0b4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a227038fdfdc272937ad5146e649480d5a2b76d616675a9847d035a9726383f9", size = 2562064, upload-time = "2026-02-26T04:12:38.358Z" },
- { url = "https://files.pythonhosted.org/packages/48/cf/02e2a9c978e80369a8b87ad53d464c8bea902ef241bcf85c580b7e283a01/curl_cffi-0.15.0b4-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d26b8ff4ba9844151fcbd407d64dbf0f6abf44d6569a4ee0fde1f65706de471b", size = 11078242, upload-time = "2026-02-26T04:12:43.553Z" },
- { url = "https://files.pythonhosted.org/packages/ab/3a/761fb2b0346dc701ef381d6d73e3701b1f63b827532abc2cfbc7078631ee/curl_cffi-0.15.0b4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:92d88529eb20c99f2d2910cb4eae18b89f12e6a34bf0f86ab52e528144877d6c", size = 11930396, upload-time = "2026-02-26T04:12:51.516Z" },
- { url = "https://files.pythonhosted.org/packages/f8/a8/c8568aff374fba82850c7e48daa2969f471459ada14e6702fecbf803b6ea/curl_cffi-0.15.0b4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:91fa67c8e6cbe7324146a383224216006000807a1df92f3de1f62d4bde0674eb", size = 2784678, upload-time = "2026-02-26T04:12:56.787Z" },
- { url = "https://files.pythonhosted.org/packages/c7/59/7a065526dab9c3289c62b0b050edf22fb4fe8233b7a424e2782c1c85253f/curl_cffi-0.15.0b4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6a8f4b710949121780ed6487bde30b05fcb9cafbd4f54e1296333153082f75c", size = 2562351, upload-time = "2026-02-26T04:12:58.266Z" },
- { url = "https://files.pythonhosted.org/packages/8f/4d/511d56dc1eb8bd9dc24a547fbed36a90ed9827f5a611362aa486b57e66f5/curl_cffi-0.15.0b4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d39934e818295d1be263f0f3118ccf1c92b90af158e61e64cd382aa907424178", size = 11085541, upload-time = "2026-02-26T04:13:01.815Z" },
- { url = "https://files.pythonhosted.org/packages/90/a2/0c1e4d9c5891fa19364a73cfe199cc584dc5725ca6e0ba24a78bbe2b6257/curl_cffi-0.15.0b4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bf95fa9980ed0f8c6207d6bd6c6f7ca2d2bb69fa3846a318d274106d6fc83ae5", size = 11936267, upload-time = "2026-02-26T04:13:05.876Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/d3/1a94fdaa7f9504b5182cd375a7785e04095ce14e48000564e4660b8f2c34/curl_cffi-0.15.0rc1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bc4c93fc3db66b258a3a03443b1dffc37321ce9ad4ca507b2ec59deae931a859", size = 2795216, upload-time = "2026-03-30T10:57:42.58Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/12/97dbe5b3c687f88dfc96d1ba90a6277a0d32135d493e11318d4e3431749b/curl_cffi-0.15.0rc1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:69c9376c308e6aea27d90b8c7885df01f3c47445c95e32dcaf1a37f91baa94e2", size = 2573500, upload-time = "2026-03-30T10:57:44.236Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/58/f8a026be4198a359142f4c34ded275bb47eeb6a44539ab932091086073df/curl_cffi-0.15.0rc1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64ac185f09d0721f1bb72409071ca5bb5270aa7ca380576d2a7ec70ef9ed317b", size = 11090389, upload-time = "2026-03-30T10:57:51.747Z" },
+ { url = "https://files.pythonhosted.org/packages/10/3f/d445ef60f7fecb944fe275661f12677a8ab1e40495b201b85b6258750278/curl_cffi-0.15.0rc1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a73a271c43967a13107080f31f315b0ef029205a9044413ce674d51316309c67", size = 11945021, upload-time = "2026-03-30T10:58:03.067Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/b5/e5fcd247f19b7dacef156871bc5bfce251426bbb87154b2266e9c724db46/curl_cffi-0.15.0rc1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:94c984ea4140c659e06a38715b91c965651935bb38499697357038f0629137f9", size = 2795678, upload-time = "2026-03-30T10:58:09.794Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/5c/b99bbb37283918bf4c5507eb47291d48d5e2669fec602d4de4cae9741f4a/curl_cffi-0.15.0rc1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7773d5f2c52f97d7748fa49537b164a965467561e73e4553d3ebc29e969a826a", size = 2573689, upload-time = "2026-03-30T10:58:11.613Z" },
+ { url = "https://files.pythonhosted.org/packages/88/60/7d1e9f17843473dfcf0439c370232946c876da576870f163f939c3612650/curl_cffi-0.15.0rc1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b487bbd7e37cdc151bb92ca391463534a5877a1d9e1d376ba76a43abbdefa578", size = 11096069, upload-time = "2026-03-30T10:58:16.015Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/26/118ad2204a3dc0e29d60e54612447b5e3e82dbc2fc8bb61c23025d553b9c/curl_cffi-0.15.0rc1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:780b4bea4aa31b59ae11458f0cc2a6d55772024bb3e111fc190c0fead376043f", size = 11949804, upload-time = "2026-03-30T10:58:21.002Z" },
]
[[package]]
@@ -575,7 +582,7 @@ wheels = [
[[package]]
name = "dashscope"
-version = "1.25.12"
+version = "1.25.15"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -585,7 +592,7 @@ dependencies = [
{ name = "websocket-client", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
wheels = [
- { url = "https://files.pythonhosted.org/packages/d9/7a/a1a9d0d293ca2cfabf54eedbea237d4d9e233251477d65c51c77667c407c/dashscope-1.25.12-py3-none-any.whl", hash = "sha256:92e98314bac0ff45b7f32f9120946771a85e614371b397f0874c1e579de60f98", size = 1342605, upload-time = "2026-02-09T09:02:50.82Z" },
+ { url = "https://files.pythonhosted.org/packages/54/39/a5a517d260e9f481e6d1e41ad7fcb021ad52327483ee2847fbb0f6350984/dashscope-1.25.15-py3-none-any.whl", hash = "sha256:0a1d2e43d8ad5447fccbbf9e29f38d7f2241c6c83c4e2c85f165a524ffb9121e", size = 1343122, upload-time = "2026-03-24T07:20:19.106Z" },
]
[[package]]
@@ -747,7 +754,7 @@ requests = [
[[package]]
name = "google-genai"
-version = "1.65.0"
+version = "1.69.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -761,9 +768,9 @@ dependencies = [
{ name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "websockets", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/79/f9/cc1191c2540d6a4e24609a586c4ed45d2db57cfef47931c139ee70e5874a/google_genai-1.65.0.tar.gz", hash = "sha256:d470eb600af802d58a79c7f13342d9ea0d05d965007cae8f76c7adff3d7a4750", size = 497206, upload-time = "2026-02-26T00:20:33.824Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/5e/c0a5e6ff60d18d3f19819a9b1fbd6a1ef2162d025696d8660550739168dc/google_genai-1.69.0.tar.gz", hash = "sha256:5f1a6a478e0c5851506a3d337534bab27b3c33120e27bf9174507ea79dfb8673", size = 519538, upload-time = "2026-03-28T15:33:27.308Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/68/3c/3fea4e7c91357c71782d7dcaad7a2577d636c90317e003386893c25bc62c/google_genai-1.65.0-py3-none-any.whl", hash = "sha256:68c025205856919bc03edb0155c11b4b833810b7ce17ad4b7a9eeba5158f6c44", size = 724429, upload-time = "2026-02-26T00:20:32.186Z" },
+ { url = "https://files.pythonhosted.org/packages/42/58/ef0586019f54b2ebb36deed7608ccb5efe1377564d2aaea6b1e295d1fadc/google_genai-1.69.0-py3-none-any.whl", hash = "sha256:252e714d724aba74949647b9de511a6a6f7804b3b317ab39ddee9cc2f001cacc", size = 760551, upload-time = "2026-03-28T15:33:24.957Z" },
]
[[package]]
@@ -1045,6 +1052,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
]
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mdurl", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
+]
+
[[package]]
name = "markdownify"
version = "1.2.2"
@@ -1108,6 +1127,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" },
]
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
[[package]]
name = "mpmath"
version = "1.3.0"
@@ -1233,7 +1261,7 @@ wheels = [
[[package]]
name = "openai"
-version = "2.24.0"
+version = "2.30.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
@@ -1245,9 +1273,9 @@ dependencies = [
{ name = "tqdm", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/88/15/52580c8fbc16d0675d516e8749806eda679b16de1e4434ea06fb6feaa610/openai-2.30.0.tar.gz", hash = "sha256:92f7661c990bda4b22a941806c83eabe4896c3094465030dd882a71abe80c885", size = 676084, upload-time = "2026-03-25T22:08:59.96Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9e/5bfa2270f902d5b92ab7d41ce0475b8630572e71e349b2a4996d14bdda93/openai-2.30.0-py3-none-any.whl", hash = "sha256:9a5ae616888eb2748ec5e0c5b955a51592e0b201a11f4262db920f2a78c5231d", size = 1146656, upload-time = "2026-03-25T22:08:58.2Z" },
]
[[package]]
@@ -1501,11 +1529,11 @@ wheels = [
[[package]]
name = "puremagic"
-version = "2.0.0"
+version = "2.1.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/dc/df/a2ee3bbf55f036acb9725b35732e3a785cb06f5c5b9fe47bde8c05ab873a/puremagic-2.0.0.tar.gz", hash = "sha256:224fe42b6b3467276a45914e12b5f40905dea0e87963adbe5289667e7c607851", size = 1119578, upload-time = "2026-02-20T13:37:53.262Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/df/3725f4b848095ef634c0b2226c97901e64ee2d5a82981d89d4b784ae8ce1/puremagic-2.1.1.tar.gz", hash = "sha256:b156c4ae63d84842f92a85cd49c9b9029a4f107f98ad14e7584ed652954feff4", size = 1133417, upload-time = "2026-03-23T19:08:46.929Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/79/03/f4a28d560494cfdf6a619c61ae5664390fac4f4b640df82e304d48f457e3/puremagic-2.0.0-py3-none-any.whl", hash = "sha256:c86aee5c15d6a346e72c5b964f1d930d42bc7dc058afdf4ac63ab92f26086aaf", size = 65924, upload-time = "2026-02-20T13:37:51.992Z" },
+ { url = "https://files.pythonhosted.org/packages/62/d0/12b1d4113fd6660a0a75e8c40500c5d1c4febd8e24dc85aaf20cfd93e9d6/puremagic-2.1.1-py3-none-any.whl", hash = "sha256:b8862451f96254358a6e2ea7fba46e0600cf8b13ebe917d6eecdb18fc22db964", size = 68025, upload-time = "2026-03-23T19:08:45.872Z" },
]
[[package]]
@@ -1658,15 +1686,15 @@ wheels = [
[[package]]
name = "pyrotgfork"
-version = "2.2.19"
+version = "2.2.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyaes", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
{ name = "pysocks", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/53/0f/80fa88993e4c64d85b1589a0b6b4d5f0679a899eeca9113dc0ea3bd7485c/pyrotgfork-2.2.19.tar.gz", hash = "sha256:bd83259db899228085a23210c465a31a4db7dfab1549047cafb2148f88e62299", size = 526410, upload-time = "2026-03-01T13:31:25.634Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d9/ef/5d5db32d5a65df1d002c8f958f1f038f9aa8e03c3133c050fd329922705e/pyrotgfork-2.2.21.tar.gz", hash = "sha256:2215a0b9dbae05f8f3aa34a56f2b7609e38bf2da9dbb35222df38f62aa7e165a", size = 530626, upload-time = "2026-03-07T12:20:09.816Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5f/70/69fff43fdf19347b8cd65393704617deb3a5232d0e09630544a497d5714f/pyrotgfork-2.2.19-py3-none-any.whl", hash = "sha256:d43d9d104d5782e3ef9dcee07f8cd23f26b226323f6ecf6cccdef2b86edbf18b", size = 5536333, upload-time = "2026-03-01T13:31:23.548Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/63/740c31095070f3abce2bd908014552e5c4b7bf729c8c31f5b4d28e4ce6fd/pyrotgfork-2.2.21-py3-none-any.whl", hash = "sha256:469aa0c404dcae5ec5b5ca5b0cd8fa97113751e39f3a73916d04bcd095b732bd", size = 5544817, upload-time = "2026-03-07T12:20:07.376Z" },
]
[[package]]
@@ -1830,6 +1858,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
]
+[[package]]
+name = "rich"
+version = "14.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+ { name = "pygments", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
+]
+
[[package]]
name = "s3transfer"
version = "0.14.0"