fix: opensource it

Signed-off-by: yihong0618 <zouzou0208@gmail.com>
This commit is contained in:
yihong0618
2025-11-24 20:33:19 +08:00
parent a5f38457af
commit 96ae20bdf8
6 changed files with 313 additions and 13 deletions

View File

@ -35,7 +35,7 @@ REPLY_MESSAGE_CACHE = ExpiringDict(max_len=1000, max_age_seconds=600)
def bot_reply_first(message: Message, who: str, bot: TeleBot) -> Message: def bot_reply_first(message: Message, who: str, bot: TeleBot) -> Message:
"""Create the first reply message which make user feel the bot is working.""" """Create the first reply message which make user feel the bot is working."""
return bot.reply_to( return bot.reply_to(
message, f"*{who}* is _thinking_ \.\.\.", parse_mode="MarkdownV2" message, f"*{who}* is _thinking_ \\.\\.\\.", parse_mode="MarkdownV2"
) )
@ -70,7 +70,7 @@ def bot_reply_markdown(
# Need a split of message # Need a split of message
msgs = smart_split(text, BOT_MESSAGE_LENGTH) msgs = smart_split(text, BOT_MESSAGE_LENGTH)
bot.edit_message_text( bot.edit_message_text(
f"*{who}* \[1/{len(msgs)}\]:\n{telegramify_markdown.markdownify(msgs[0])}", f"*{who}* \\[1/{len(msgs)}\\]:\n{telegramify_markdown.markdownify(msgs[0])}",
chat_id=reply_id.chat.id, chat_id=reply_id.chat.id,
message_id=reply_id.message_id, message_id=reply_id.message_id,
parse_mode="MarkdownV2", parse_mode="MarkdownV2",
@ -79,7 +79,7 @@ def bot_reply_markdown(
for i in range(1, len(msgs)): for i in range(1, len(msgs)):
bot.reply_to( bot.reply_to(
reply_id.reply_to_message, reply_id.reply_to_message,
f"*{who}* \[{i + 1}/{len(msgs)}\\]:\n{telegramify_markdown.markdownify(msgs[i])}", f"*{who}* \\[{i + 1}/{len(msgs)}\\]:\n{telegramify_markdown.markdownify(msgs[i])}",
parse_mode="MarkdownV2", parse_mode="MarkdownV2",
) )

View File

@ -103,8 +103,8 @@ def gemini_handler(message: Message, bot: TeleBot) -> None:
player.send_message(m) player.send_message(m)
gemini_reply_text = player.last.text.strip() gemini_reply_text = player.last.text.strip()
# Gemini is often using ':' in **Title** which not work in Telegram Markdown # Gemini is often using ':' in **Title** which not work in Telegram Markdown
gemini_reply_text = gemini_reply_text.replace(":**", "\:**") gemini_reply_text = gemini_reply_text.replace(":**", "\\:**")
gemini_reply_text = gemini_reply_text.replace("**", "**\: ") gemini_reply_text = gemini_reply_text.replace("**", "**\\: ")
except StopCandidateException as e: except StopCandidateException as e:
match = re.search(r'content\s*{\s*parts\s*{\s*text:\s*"([^"]+)"', str(e)) match = re.search(r'content\s*{\s*parts\s*{\s*text:\s*"([^"]+)"', str(e))
if match: if match:

View File

@ -1,9 +1,12 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import random
import zoneinfo
from datetime import datetime, timezone from datetime import datetime, timezone
from functools import partial from functools import partial
import shlex import shlex
import threading
import telegramify_markdown import telegramify_markdown
from telebot import TeleBot from telebot import TeleBot
@ -14,11 +17,17 @@ from config import settings
from handlers._utils import non_llm_handler from handlers._utils import non_llm_handler
from .messages import ChatMessage, MessageStore from .messages import ChatMessage, MessageStore
from .utils import PROMPT, filter_message, parse_date from .utils import PROMPT, filter_message, parse_date, contains_non_ascii
from datetime import timedelta
from rich import print
logger = logging.getLogger("bot") logger = logging.getLogger("bot")
store = MessageStore("data/messages.db") store = MessageStore("data/messages.db")
# 从环境变量获取提肛群组 ID
TIGONG_CHAT_ID = settings.tigong_chat_id
def get_display_width(text: str) -> int: def get_display_width(text: str) -> int:
"""获取字符串的显示宽度,考虑中文字符""" """获取字符串的显示宽度,考虑中文字符"""
@ -34,14 +43,44 @@ def pad_to_width(text: str, target_width: int) -> str:
@non_llm_handler @non_llm_handler
def handle_message(message: Message): def handle_message(message: Message, bot: TeleBot):
logger.debug( logger.debug(
"Received message: %s, chat_id=%d, from=%s", "Received message: %s, chat_id=%d, from=%s",
message.text, message.text,
message.chat.id, message.chat.id,
message.from_user.id, message.from_user.id,
) )
# 这里可以添加处理消息的逻辑
# 检测中文消息并删除(仅在特定时间和群组)
# 只在提肛群组且每天北京时间 15:00-16:00 之间删除
if (
TIGONG_CHAT_ID
and message.chat.id == TIGONG_CHAT_ID
and message.text
and contains_non_ascii(message.text)
):
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
current_time = datetime.now(tz=beijing_tz)
current_hour = current_time.hour
# 检查是否在北京时间 15:00-16:00 之间
if 15 <= current_hour < 16:
try:
bot.delete_message(message.chat.id, message.message_id)
bot.send_message(
message.chat.id,
f"已删除 @{message.from_user.username or message.from_user.full_name} 的中文消息",
)
logger.info(
"Deleted Chinese message from user %s in chat %d at %s",
message.from_user.full_name,
message.chat.id,
current_time.strftime("%H:%M:%S"),
)
return
except Exception as e:
logger.error("Failed to delete message: %s", e)
store.add_message( store.add_message(
ChatMessage( ChatMessage(
chat_id=message.chat.id, chat_id=message.chat.id,
@ -53,6 +92,18 @@ def handle_message(message: Message):
) )
) )
# 检测100整数倍消息提醒
if TIGONG_CHAT_ID and message.chat.id == TIGONG_CHAT_ID:
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
today = datetime.now(tz=beijing_tz).strftime("%Y-%m-%d")
count = store.get_today_message_count(message.chat.id, today)
if count > 0 and count % 100 == 0:
bot.send_message(
message.chat.id,
f"🎉 今日第 {count} 条消息!提肛小助手提醒:该做提肛运动啦!",
)
@non_llm_handler @non_llm_handler
def summary_command(message: Message, bot: TeleBot): def summary_command(message: Message, bot: TeleBot):
@ -110,7 +161,7 @@ def stats_command(message: Message, bot: TeleBot):
if len(text_args) > 1 and text_args[1].isdigit(): if len(text_args) > 1 and text_args[1].isdigit():
limit = int(text_args[1]) limit = int(text_args[1])
else: else:
limit = 10 limit = 30
user_stats = store.get_user_stats(message.chat.id, limit=limit) user_stats = store.get_user_stats(message.chat.id, limit=limit)
if user_stats: if user_stats:
# 计算用户消息数量的最大宽度 # 计算用户消息数量的最大宽度
@ -124,11 +175,11 @@ def stats_command(message: Message, bot: TeleBot):
else: else:
user_text = "" user_text = ""
return_message = f"📊 群组消息统计信息:\n```\n{stats_text}\n```\n👤 用户消息统计信息:\n```\n{user_text}\n```\\-\\-\\-\n"
bot.reply_to( bot.reply_to(
message, message,
( return_message,
f"📊 群组消息统计信息:\n```\n{stats_text}\n```\n👤 用户消息统计信息:\n```\n{user_text}\n```"
),
parse_mode="MarkdownV2", parse_mode="MarkdownV2",
) )
@ -164,6 +215,137 @@ def search_command(message: Message, bot: TeleBot):
) )
TIGONG_MESSAGES = [
"💪 提肛时间到!记得做提肛运动哦~",
"🏋️ 该做提肛运动了!坚持就是胜利!",
"⏰ 提肛小助手提醒:现在是提肛时间!",
"🎯 提肛运动打卡时间!加油!",
"💯 定时提醒:做做提肛运动,健康生活每一天!",
"🌟 提肛运动不能停!现在开始吧!",
"✨ 提肛小助手:该运动啦!",
]
@non_llm_handler
def alert_me_command(message: Message, bot: TeleBot):
"""加入提肛提醒队列"""
if TIGONG_CHAT_ID and message.chat.id == TIGONG_CHAT_ID:
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
today = datetime.now(tz=beijing_tz).strftime("%Y-%m-%d")
username = message.from_user.username or ""
store.add_tigong_alert_user(
message.chat.id,
message.from_user.id,
message.from_user.full_name,
username,
today,
)
bot.reply_to(
message,
"✅ 已加入今日提肛提醒队列!每次提醒都会 @ 你,记得 /confirm 打卡哦!",
)
else:
bot.reply_to(message, "此命令仅在指定群组中可用。")
@non_llm_handler
def confirm_command(message: Message, bot: TeleBot):
"""确认完成今日提肛"""
if TIGONG_CHAT_ID and message.chat.id == TIGONG_CHAT_ID:
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
today = datetime.now(tz=beijing_tz).strftime("%Y-%m-%d")
success = store.confirm_tigong_alert(
message.chat.id, message.from_user.id, today
)
if success:
bot.reply_to(message, "✅ 今日提肛已打卡!明天继续加油!")
else:
bot.reply_to(message, "你还没有加入提醒队列,请先使用 /alert_me 加入。")
else:
bot.reply_to(message, "此命令仅在指定群组中可用。")
@non_llm_handler
def standup_command(message: Message, bot: TeleBot):
"""手动发送提肛提醒消息"""
if TIGONG_CHAT_ID and message.chat.id == TIGONG_CHAT_ID:
try:
send_random_tigong_reminder(bot)
# 不需要reply因为send_random_tigong_reminder已经发送消息了
except Exception as e:
logger.error("Error in standup_command: %s", e)
bot.reply_to(message, "❌ 发送提醒失败,请稍后重试。")
else:
bot.reply_to(message, "此命令仅在指定群组中可用。")
def send_random_tigong_reminder(bot: TeleBot):
"""发送随机提肛提醒消息"""
try:
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
today = datetime.now(tz=beijing_tz).strftime("%Y-%m-%d")
# 获取未确认用户列表
unconfirmed_users = store.get_unconfirmed_users(TIGONG_CHAT_ID, today)
message = random.choice(TIGONG_MESSAGES)
# 如果有未确认用户,@他们
if unconfirmed_users:
message += "\n\n"
mentions = []
for user in unconfirmed_users:
# 使用 username 或者 text mention
username = user.get("username", "")
if username:
mentions.append(f"@{username}")
else:
# 如果没有 username使用名字但不能点击
mentions.append(user["user_name"])
message += " ".join(mentions) + " 记得打卡哦!"
# 发送消息
bot.send_message(TIGONG_CHAT_ID, message)
logger.info(
"Sent tigong reminder to chat %d with %d mentions",
TIGONG_CHAT_ID,
len(unconfirmed_users),
)
except Exception as e:
logger.error("Failed to send tigong reminder: %s", e, exc_info=True)
raise
def schedule_tigong_reminders(bot: TeleBot):
"""安排提肛提醒任务每天北京时间8:00-19:00每2小时发送一次"""
def run_scheduler():
import time
beijing_tz = zoneinfo.ZoneInfo("Asia/Shanghai")
while True:
now = datetime.now(tz=beijing_tz)
current_hour = now.hour
# 检查是否在北京时间8:00-19:00之间
if 8 <= current_hour < 19:
# 检查是否在偶数小时的整点8, 10, 12, 14, 16, 18
if current_hour % 2 == 0 and now.minute == 0 and now.second < 30:
send_random_tigong_reminder(bot)
time.sleep(30) # 避免在同一分钟内重复发送
# 每30秒检查一次
time.sleep(30)
# 在后台线程中运行调度器
scheduler_thread = threading.Thread(target=run_scheduler, daemon=True)
scheduler_thread.start()
logger.info("Tigong reminder scheduler started")
load_priority = 5 load_priority = 5
if settings.openai_api_key: if settings.openai_api_key:
@ -175,5 +357,17 @@ if settings.openai_api_key:
bot.register_message_handler(stats_command, commands=["stats"], pass_bot=True) bot.register_message_handler(stats_command, commands=["stats"], pass_bot=True)
bot.register_message_handler(search_command, commands=["search"], pass_bot=True) bot.register_message_handler(search_command, commands=["search"], pass_bot=True)
bot.register_message_handler( bot.register_message_handler(
handle_message, func=partial(filter_message, bot=bot) standup_command, commands=["standup"], pass_bot=True
) )
bot.register_message_handler(
alert_me_command, commands=["alert_me"], pass_bot=True
)
bot.register_message_handler(
confirm_command, commands=["confirm"], pass_bot=True
)
bot.register_message_handler(
handle_message, func=partial(filter_message, bot=bot), pass_bot=True
)
# 启动提肛提醒定时任务
schedule_tigong_reminders(bot)

View File

@ -59,6 +59,19 @@ class MessageStore:
CREATE INDEX IF NOT EXISTS idx_chat_timestamp ON messages (chat_id, timestamp); CREATE INDEX IF NOT EXISTS idx_chat_timestamp ON messages (chat_id, timestamp);
""" """
) )
conn.execute(
"""
CREATE TABLE IF NOT EXISTS tigong_alerts (
chat_id INTEGER,
user_id INTEGER,
user_name TEXT,
username TEXT,
date TEXT,
confirmed INTEGER DEFAULT 0,
PRIMARY KEY (chat_id, user_id, date)
);
"""
)
conn.commit() conn.commit()
def add_message( def add_message(
@ -188,3 +201,63 @@ class MessageStore:
"DELETE FROM messages WHERE chat_id = ? AND timestamp < ?;", "DELETE FROM messages WHERE chat_id = ? AND timestamp < ?;",
(chat_id, threshold_date.isoformat()), (chat_id, threshold_date.isoformat()),
) )
def add_tigong_alert_user(
self, chat_id: int, user_id: int, user_name: str, username: str, date: str
) -> None:
"""添加用户到提肛提醒队列"""
with self.connect() as conn:
conn.execute(
"""
INSERT OR REPLACE INTO tigong_alerts (chat_id, user_id, user_name, username, date, confirmed)
VALUES (?, ?, ?, ?, ?, 0);
""",
(chat_id, user_id, user_name, username, date),
)
conn.commit()
def confirm_tigong_alert(self, chat_id: int, user_id: int, date: str) -> bool:
"""确认用户完成提肛"""
with self.connect() as conn:
cursor = conn.cursor()
cursor.execute(
"""
UPDATE tigong_alerts SET confirmed = 1
WHERE chat_id = ? AND user_id = ? AND date = ?;
""",
(chat_id, user_id, date),
)
conn.commit()
return cursor.rowcount > 0
def get_unconfirmed_users(self, chat_id: int, date: str) -> list[dict]:
"""获取当天未确认的用户列表"""
with self.connect() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT user_id, user_name, username
FROM tigong_alerts
WHERE chat_id = ? AND date = ? AND confirmed = 0;
""",
(chat_id, date),
)
rows = cursor.fetchall()
return [
{"user_id": row[0], "user_name": row[1], "username": row[2]} for row in rows
]
def get_today_message_count(self, chat_id: int, date_str: str) -> int:
"""获取当天的消息数量"""
with self.connect() as conn:
cursor = conn.cursor()
cursor.execute(
"""
SELECT COUNT(*)
FROM messages
WHERE chat_id = ? AND DATE(timestamp) = ?;
""",
(chat_id, date_str),
)
result = cursor.fetchone()
return result[0] if result else 0

View File

@ -14,6 +14,10 @@ PROMPT = """\
""" """
def contains_non_ascii(text: str) -> bool:
return not text.isascii()
def filter_message(message: Message, bot: TeleBot) -> bool: def filter_message(message: Message, bot: TeleBot) -> bool:
"""过滤消息,排除非文本消息和命令消息""" """过滤消息,排除非文本消息和命令消息"""
if not message.text: if not message.text:

29
init_tigong_db.py Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""初始化提肛提醒数据库"""
from handlers.summary.messages import MessageStore
def main():
print("正在初始化提肛提醒数据库...")
# 初始化数据库
store = MessageStore("data/messages.db")
print("✅ 数据库初始化完成!")
print("\n数据库位置: data/messages.db")
print("\n已创建的表:")
print(" - messages: 存储聊天消息")
print(" - tigong_alerts: 存储提肛提醒用户队列")
print("\n可用的命令:")
print(" /alert_me - 加入提肛提醒队列")
print(" /confirm - 确认完成今日提肛")
print(" /standup - 手动发送提肛提醒")
print("\n功能:")
print(" - 每天北京时间 8:00-18:00每2小时自动提醒")
print(" - 每达到100条消息的整数倍时自动提醒")
print(" - 提醒会 @ 所有未确认的用户")
if __name__ == "__main__":
main()