diff --git a/base/func_news.py b/base/func_news.py
index 2817e70..dcf27ce 100644
--- a/base/func_news.py
+++ b/base/func_news.py
@@ -47,37 +47,6 @@ class News(object):
return f"{fmt_time} {self.week[weekday_news]}\n{fmt_news}"
-
- def get_36kr_news(self):
- url = "https://orz.ai/dailynews/?platform=36kr"
- # 获取当前日期和英文星期名
- now = datetime.now()
- current_date = now.strftime("%Y年%m月%d日")
- english_weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
- chinese_weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
-
- # 将英文星期名映射为中文
- current_weekday_index = now.weekday() # 获取当前是星期几(0代表星期一,6代表星期日)
- current_weekday_chinese = chinese_weekdays[current_weekday_index]
-
- # 初始化一个空字符串来存储结果
- output = f"当前日期:{current_date} {current_weekday_chinese}\n\n"
-
- response = requests.get(url)
-
- if response.status_code == 200:
- post = response.json()
- str = post['data']
- # 遍历列表,并格式化每个字典的title, url,然后添加到output字符串中
- for index, article in enumerate(str, start=1):
- title = article['title']
- url = article['url']
- # 使用f-string格式化字符串,并添加到output中
- output += f"{index}. 标题: {title}\n URL: {url}\n"
-
- # 输出最终的字符串(这里只是为了展示,实际上你可以根据需要处理这个字符串)
- return output
-
def get_baidu_news(self):
url = "https://top.baidu.com/api/board?platform=wise&tab=realtime"
# 获取当前日期和英文星期名
diff --git a/base/func_report_reminder.py b/base/func_report_reminder.py
index ce0661a..f30b1ba 100644
--- a/base/func_report_reminder.py
+++ b/base/func_report_reminder.py
@@ -18,16 +18,16 @@ class ReportReminder:
today = datetime.datetime.now().date()
# 如果是非工作日
if not is_workday(today):
- robot.sendTextMsg("休息日快乐", receiver)
+ robot.send_text_msg("休息日快乐", receiver)
# 如果是工作日
if is_workday(today):
- robot.sendTextMsg("该发日报啦", receiver)
+ robot.send_text_msg("该发日报啦", receiver)
# 如果是本周最后一个工作日
if ReportReminder.last_work_day_of_week(today) == today:
- robot.sendTextMsg("该发周报啦", receiver)
+ robot.send_text_msg("该发周报啦", receiver)
# 如果本日是本月最后一整周的最后一个工作日:
if ReportReminder.last_work_friday_of_month(today) == today:
- robot.sendTextMsg("该发月报啦", receiver)
+ robot.send_text_msg("该发月报啦", receiver)
# 计算本月最后一个周的最后一个工作日
@staticmethod
diff --git a/config.yaml b/config.yaml
index 8acc001..4daf893 100644
--- a/config.yaml
+++ b/config.yaml
@@ -120,3 +120,24 @@ doubao:
- 问题评价:分析问题的提出角度,如(财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐)等
- 总结:经过300个字以内的优化返回,返回内容请进行一定程度的结构化,方便快速阅读' # 根据需要对角色进行设定
+
+
+# config.yaml
+
+db_config:
+ pool_name: "wechat_boot_pool"
+ pool_size: 10
+ host: "192.168.2.32"
+ user: "root"
+ password: "lw123456"
+ database: "message_archive"
+ charset: "utf8mb4"
+ use_unicode: true
+ get_warnings: true
+ pool_reset_session: true
+
+redis_config:
+ host: "192.168.2.32"
+ port: 6379
+ db: 0
+ decode_responses: true
\ No newline at end of file
diff --git a/configuration.py b/configuration.py
index 691e7fc..959b193 100644
--- a/configuration.py
+++ b/configuration.py
@@ -40,3 +40,7 @@ class Config(object):
self.CLAUDE = yconfig.get("claude", {})
self.DEEPSEEK = yconfig.get("deepseek", {})
self.DOUBAO = yconfig.get("doubao", {})
+
+ # DB config
+ self.mariadb = yconfig.get("db_config", {})
+ self.redis = yconfig.get("redis_config", {})
diff --git a/group_auto/group_member_change.py b/group_auto/group_member_change.py
index 0133be1..7650da2 100644
--- a/group_auto/group_member_change.py
+++ b/group_auto/group_member_change.py
@@ -4,15 +4,18 @@ import xml.etree.ElementTree as ET
from wcferry import Wcf
-
class GroupMemberChange:
- def __init__(self, wcf: Wcf):
+ def __init__(self, wcf: Wcf, redis_pool: redis.ConnectionPool):
self.wcf = wcf # 假设 wcf 对象在此类中初始化
- self.r = redis.Redis(host='192.168.2.32', port=6379, db=0, decode_responses=True)
+ self.redis_pool = redis_pool
# 初始化本地缓存字典,使用 group_id 作为键
self.local_membercounts = {}
self.local_members = {}
+ def _get_redis_connection(self):
+ """从连接池获取 Redis 连接"""
+ return redis.Redis(connection_pool=self.redis_pool)
+
def get_current_members(self, group_id):
""" 获取当前群成员信息 """
print(f"Fetching current members for group_id: {group_id}")
@@ -27,8 +30,8 @@ class GroupMemberChange:
# 读取 Redis 中的数据
print(f"Fetching previous data from Redis for group_id: {group_id}")
- membercount_previous = self.r.get(membercount_key)
- members_previous = self.r.hgetall(members_key) # 获取上次的成员信息
+ membercount_previous = self._get_redis_connection().get(membercount_key)
+ members_previous = self._get_redis_connection().hgetall(members_key) # 获取上次的成员信息
print(f"Previous membercount: {membercount_previous}, Previous members: {members_previous}")
return membercount_previous, members_previous
@@ -48,9 +51,10 @@ class GroupMemberChange:
if membercount_previous is None or not members_previous:
print("First time processing, saving current data to Redis")
members_current = self.get_current_members(group_id)
- self.r.set(f"group:group_member_count:{group_id}", membercount_current)
- self.r.delete(f"group:group_members:{group_id}")
- self.r.hset(f"group:group_members:{group_id}", mapping=members_current) # 存储当前成员信息
+ self._get_redis_connection().set(f"group:group_member_count:{group_id}", membercount_current)
+ self._get_redis_connection().delete(f"group:group_members:{group_id}")
+ self._get_redis_connection().hset(f"group:group_members:{group_id}",
+ mapping=members_current) # 存储当前成员信息
# 更新本地缓存
self.local_membercounts[group_id] = membercount_current
@@ -99,11 +103,11 @@ class GroupMemberChange:
# 更新 Redis 数据
print(f"Updating Redis with current membercount and members")
- self.r.set(f"group:group_member_count:{group_id}", membercount_current)
+ self._get_redis_connection.set(f"group:group_member_count:{group_id}", membercount_current)
- self.r.delete(f"group:group_members:{group_id}")
+ self._get_redis_connection.delete(f"group:group_members:{group_id}")
# 更新 Redis 中的成员信息,确保在成员变化时也更新
- self.r.hset(f"group:group_members:{group_id}", mapping=members_current)
+ self._get_redis_connection.hset(f"group:group_members:{group_id}", mapping=members_current)
else:
result.append("$NO_CHANGE$")
diff --git a/main.py b/main.py
index a9908f8..e0b386b 100644
--- a/main.py
+++ b/main.py
@@ -25,25 +25,25 @@ def main(chat_type: int):
robot.LOG.info(f"WeChatRobot【{__version__}】成功启动···")
# 机器人启动发送测试消息
- robot.sendTextMsg("机器人启动成功!", "filehelper")
+ robot.send_text_msg("机器人启动成功!", "filehelper")
# 接收消息
# robot.enableRecvMsg() # 可能会丢消息?
robot.enableReceivingMsg() # 加队列
# 每天 8:30 发送新闻
- robot.onEveryTime("08:30", robot.newsBaiduReportAuto)
+ robot.onEveryTime("08:30", robot.news_baidu_report_auto)
# epic
- robot.onEveryTime("10:30", robot.sendEpicFreeGames)
+ robot.onEveryTime("10:30", robot.send_epic_free_games)
# message report 1:数据自动从redis 转到sqllite
- robot.onEveryTime("00:30", robot.messageCountToDB)
+ robot.onEveryTime("00:30", robot.message_count_to_db)
# 从db中提取并发送给相关群
- robot.onEveryTime("09:30", robot.generateAndSendRanking)
+ robot.onEveryTime("09:30", robot.generate_and_send_ranking)
# sehuatang
- robot.onEveryTime("15:00", robot.generateSehuatangPdf)
+ robot.onEveryTime("15:00", robot.generate_sehuatang_pdf)
# 游戏的定时任务每小时执行
robot.onEveryTime("18:00", robot.game_auto_tasks)
@@ -55,7 +55,7 @@ def main(chat_type: int):
robot.onEveryTime("17:30", robot.xiu_ren_pdf_send)
# 让机器人一直跑
- robot.keepRunningAndBlockProcess()
+ robot.keep_running_and_block_process()
if __name__ == "__main__":
diff --git a/message_sign/config.toml b/message_sign/config.toml
new file mode 100644
index 0000000..8bfce4b
--- /dev/null
+++ b/message_sign/config.toml
@@ -0,0 +1,7 @@
+[SignIn]
+enable = true
+command = ["签到", "每日签到", "qd", "Qd", "QD"]
+min-point = 3
+max-point = 20
+streak-cycle = 5 # 每签到?天后,额外积分奖励加1点?
+max-streak-point = 10 # 额外积分奖励上限
\ No newline at end of file
diff --git a/message_sign/main.py b/message_sign/main.py
new file mode 100644
index 0000000..3de12c6
--- /dev/null
+++ b/message_sign/main.py
@@ -0,0 +1,233 @@
+import datetime
+import logging
+import mysql.connector.pooling
+import tomllib
+import pytz
+import redis
+from typing import Optional, Tuple
+
+from wcferry import Wcf, WxMsg
+from robot_cmd.robot_command import GroupBotManager, Feature, PermissionStatus
+
+# 创建表的SQL语句
+CREATE_TABLE_SQL = """
+CREATE TABLE IF NOT EXISTS t_sign_record (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ wx_id VARCHAR(100) NOT NULL,
+ group_id VARCHAR(100) NOT NULL,
+ wx_nick_name VARCHAR(100) NOT NULL,
+ points INT DEFAULT 0,
+ sign_stat DATETIME,
+ signin_streak INT DEFAULT 0,
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY unique_sign (wx_id, group_id)
+)
+"""
+
+
+class SignInSystem:
+ def __init__(self, wcf: Wcf, gbm: GroupBotManager, all_contacts: dict,
+ db_pool: mysql.connector.pooling.MySQLConnectionPool, redis_pool: redis.ConnectionPool):
+ # 读取配置文件
+ with open('message_sign/config.toml', 'rb') as f:
+ self.config = tomllib.load(f)['SignIn']
+
+ self.LOG = logging.getLogger(__name__)
+
+ if not self.config['enable']:
+ raise Exception("签到功能未启用")
+
+ self.wcf = wcf
+ self.gbm = gbm
+ self.all_contacts = all_contacts
+ self.db_pool = db_pool
+ self.redis_pool = redis_pool
+ self.command = self.config['command']
+ self.min_point = self.config['min-point']
+ self.max_point = self.config['max-point']
+ self.streak_cycle = self.config['streak-cycle']
+ self.max_streak_point = self.config['max-streak-point']
+ # 时区设置
+ self.timezone = 'Asia/Shanghai'
+
+ # 从 Redis 初始化签到数据
+ self.today_signin_count = self._load_signin_count_from_redis()
+ with self._get_redis_connection() as redis_client:
+ last_reset_date_str = redis_client.get('group:sign_in:last_reset_date')
+ if last_reset_date_str:
+ self.last_reset_date = datetime.datetime.strptime(last_reset_date_str, '%Y-%m-%d').date()
+ else:
+ self.last_reset_date = datetime.datetime.now(tz=pytz.timezone(self.timezone)).date()
+ self._save_last_reset_date_to_redis()
+
+ self.LOG.info(f"[签到] 组件初始化完成 {self.command_format}")
+
+ def _get_db_connection(self):
+ """从连接池获取数据库连接"""
+ return self.db_pool.get_connection()
+
+ def _get_redis_connection(self):
+ """从连接池获取 Redis 连接"""
+ return redis.Redis(connection_pool=self.redis_pool)
+
+ def _load_signin_count_from_redis(self) -> dict:
+ """从 Redis 加载签到人数数据"""
+ signin_count = {}
+ with self._get_redis_connection() as redis_client:
+ keys = redis_client.keys('group:sign_in:*')
+ for key in keys:
+ if key == 'group:sign_in:last_reset_date':
+ continue
+ group_id = key.replace('group:sign_in:', '')
+ count = redis_client.get(key)
+ if count is not None:
+ signin_count[group_id] = int(count)
+ return signin_count
+
+ def _save_signin_count_to_redis(self, group_id: str, count: int):
+ """保存签到人数到 Redis"""
+ with self._get_redis_connection() as redis_client:
+ redis_client.set(f'group:sign_in:{group_id}', count)
+
+ def _save_last_reset_date_to_redis(self):
+ """保存最后重置日期到 Redis"""
+ with self._get_redis_connection() as redis_client:
+ redis_client.set('group:sign_in:last_reset_date', self.last_reset_date.strftime('%Y-%m-%d'))
+
+ @property
+ def command_format(self):
+ return ','.join(self.command)
+
+ @property
+ def enable(self):
+ return self.config['enable']
+
+ def initialize_table(self):
+ """初始化数据库表"""
+ with self._get_db_connection() as conn:
+ with conn.cursor(dictionary=True) as cursor: # 使用 dictionary=True 返回字典格式
+ cursor.execute(CREATE_TABLE_SQL)
+ conn.commit()
+
+ def reset_today_count_if_needed(self):
+ """检查并重置每日签到计数"""
+ current_date = datetime.datetime.now(tz=pytz.timezone(self.timezone)).date()
+ if current_date != self.last_reset_date:
+ self.today_signin_count.clear()
+ with self._get_redis_connection() as redis_client:
+ keys = redis_client.keys('group:sign_in:*')
+ for key in keys:
+ if key != 'group:sign_in:last_reset_date':
+ redis_client.delete(key)
+ self.last_reset_date = current_date
+ self._save_last_reset_date_to_redis()
+ self.LOG.info(f"[签到] 已重置每日签到计数,日期更新为 {current_date}")
+
+ def get_today_signin_count(self, group_id: str) -> int:
+ """获取群内今日签到人数(使用缓存)"""
+ self.reset_today_count_if_needed()
+ return self.today_signin_count.get(group_id, 0)
+
+ def get_user_record(self, wx_id: str, group_id: str) -> Optional[dict]:
+ """获取用户签到记录"""
+ with self._get_db_connection() as conn:
+ with conn.cursor(dictionary=True) as cursor:
+ query = """
+ SELECT wx_id, group_id, wx_nick_name, points, sign_stat, signin_streak
+ FROM t_sign_record
+ WHERE wx_id = %s AND group_id = %s
+ """
+ cursor.execute(query, (wx_id, group_id))
+ return cursor.fetchone()
+
+ def calculate_points(self, streak: int) -> int:
+ """根据连续签到天数计算积分"""
+ base_points = self.min_point
+ extra_points = min(streak // self.streak_cycle, self.max_streak_point)
+ total_points = base_points + extra_points
+ return min(total_points, self.max_point)
+
+ def member_sign_in(self, message: WxMsg):
+ """会员签到功能"""
+ if not self.enable:
+ return
+
+ content = str(message.content).strip()
+ command = content.split(" ")
+ if not len(command) or command[0] not in self.command:
+ return
+
+ if self.gbm.get_group_permission(message.roomid, Feature.TASK_GAME) == PermissionStatus.DISABLED:
+ return
+
+ current_time = datetime.datetime.now(tz=pytz.timezone(self.timezone))
+ today_start = current_time.replace(hour=0, minute=0, second=0, microsecond=0)
+ yesterday = today_start - datetime.timedelta(days=1)
+
+ user_record = self.get_user_record(message.sender, message.roomid)
+ wx_nick_name = self.all_contacts.get(message.sender, message.sender)
+
+ if user_record and user_record['sign_stat'] and user_record['sign_stat'] >= today_start:
+ self.wcf.send_text(
+ f"@{wx_nick_name} 您今天已经签到过了!当前积分:{user_record['points']}",
+ message.sender
+ )
+ return
+
+ streak = 0
+ if user_record and user_record['sign_stat']:
+ last_sign_date = user_record['sign_stat'].replace(hour=0, minute=0, second=0, microsecond=0)
+ if last_sign_date == yesterday:
+ streak = user_record['signin_streak'] + 1
+ else:
+ streak = 1
+ else:
+ streak = 1
+
+ today_signin_rank = self.get_today_signin_count(message.roomid) + 1
+ self.today_signin_count[message.roomid] = today_signin_rank
+ self._save_signin_count_to_redis(message.roomid, today_signin_rank)
+
+ points_to_add = self.calculate_points(streak)
+
+ with self._get_db_connection() as conn:
+ with conn.cursor(dictionary=True) as cursor:
+ if user_record:
+ update_sql = """
+ UPDATE t_sign_record
+ SET wx_nick_name = %s, points = points + %s,
+ sign_stat = %s, signin_streak = %s,
+ update_time = %s
+ WHERE wx_id = %s AND group_id = %s
+ """
+ cursor.execute(update_sql, (
+ wx_nick_name, points_to_add, current_time, streak,
+ current_time, message.sender, message.roomid
+ ))
+ else:
+ insert_sql = """
+ INSERT INTO t_sign_record
+ (wx_id, group_id, wx_nick_name, points, sign_stat, signin_streak)
+ VALUES (%s, %s, %s, %s, %s, %s)
+ """
+ cursor.execute(insert_sql, (
+ message.sender, message.roomid, wx_nick_name, points_to_add, current_time, streak
+ ))
+ conn.commit()
+
+ msg = (
+ f"@{wx_nick_name} 签到成功!\n"
+ f"您是今日群内第{today_signin_rank}个签到的\n"
+ f"连续签到{streak}天,本次获得{points_to_add}积分"
+ )
+ self.wcf.send_text(
+ msg,
+ (message.roomid if message.from_group() else message.sender),
+ message.sender
+ )
+
+ def __del__(self):
+ """连接池由外部管理,不需要手动关闭"""
+ pass
+
diff --git a/message_storage/message_to_db.py b/message_storage/message_to_db.py
index 58f2ee1..3cf1bf1 100644
--- a/message_storage/message_to_db.py
+++ b/message_storage/message_to_db.py
@@ -4,120 +4,109 @@ import redis
import xml.etree.ElementTree as ET
from message_summary.message_summary_4o import message_summary
-# 配置数据库连接
-db_config = {
- 'host': '192.168.2.32', # 替换为你的MariaDB服务器地址
- 'user': 'root', # 替换为你的MariaDB用户名
- 'password': 'lw123456', # 替换为你的MariaDB密码
- 'database': 'message_archive'
-}
-
-# 连接到Redis
-r = redis.Redis(host='192.168.2.32', port=6379, db=0)
+import mysql.connector.pooling
-def archive_message(group_id, timestamp_str, sender, content, message_type, attachment_url=None):
- # 连接到数据库
- connection = pymysql.connect(**db_config)
+class MessageStorage:
- try:
- with connection.cursor() as cursor:
- # 插入消息信息
- sql = """
- INSERT INTO messages (group_id,timestamp, sender, content, message_type, attachment_url)
- VALUES (%s, %s, %s, %s, %s, %s)
- """
- cursor.execute(sql, (group_id, timestamp_str, sender, content, message_type, attachment_url))
+ def __init__(self, db_pool: mysql.connector.pooling.MySQLConnectionPool, redis_pool: redis.ConnectionPool):
+ self.redis_pool = redis_pool
+ self.db_pool = db_pool
+ # 初始化本地缓存字典,使用 group_id 作为键
+ self.local_membercounts = {}
+ self.local_members = {}
- # 提交事务
- connection.commit()
- print(f"Archived:{timestamp_str}:{group_id}:{sender}: {content}")
+ def _get_redis_connection(self):
+ """从连接池获取 Redis 连接"""
+ return redis.Redis(connection_pool=self.redis_pool)
- except Exception as e:
- print(f"Error archiving message: {e}")
- connection.rollback()
+ def archive_message(self, group_id, timestamp_str, sender, content, message_type, attachment_url=None):
+ # 连接到数据库
+ connection = self.db_pool.get_connection()
- finally:
- # 关闭连接
- connection.close()
+ try:
+ with connection.cursor() as cursor:
+ # 插入消息信息
+ sql = """
+ INSERT INTO messages (group_id,timestamp, sender, content, message_type, attachment_url)
+ VALUES (%s, %s, %s, %s, %s, %s)
+ """
+ cursor.execute(sql, (group_id, timestamp_str, sender, content, message_type, attachment_url))
+ # 提交事务
+ connection.commit()
+ print(f"Archived:{timestamp_str}:{group_id}:{sender}: {content}")
-def get_messages(group_id, all_contacts: dict):
- # 连接到数据库
- with pymysql.connect(**db_config) as connection:
- # 获取 redis 中的上次总结时间,本次从上次开始算,若没有,则从 8 小时之前开始计算
- key = f"{group_id}:summary_time"
- last_summary_time = r.get(key)
+ except Exception as e:
+ print(f"Error archiving message: {e}")
+ connection.rollback()
- # 如果 Redis 返回值为字节类型,转换为字符串
- if last_summary_time:
- last_summary_time = last_summary_time.decode('utf-8')
+ finally:
+ # 关闭连接
+ connection.close()
- current_time = datetime.now()
- current_date = current_time.strftime('%Y-%m-%d %H:%M:%S')
+ def get_messages(self, group_id, all_contacts: dict):
+ # 连接到数据库
+ with self.db_pool.get_connection() as connection:
+ # 获取 redis 中的上次总结时间,本次从上次开始算,若没有,则从 8 小时之前开始计算
+ key = f"{group_id}:summary_time"
+ last_summary_time = self._get_redis_connection().get(key)
- if not last_summary_time:
- # 获取当前时间并计算 8 小时前的时间
- eight_hours_ago = current_time - timedelta(hours=8)
- last_summary_time = eight_hours_ago.strftime('%Y-%m-%d %H:%M:%S')
- else:
- # 检查 redis 中的时间与当前时间差是否小于 3 小时
- last_summary_time_obj = datetime.strptime(last_summary_time, '%Y-%m-%d %H:%M:%S')
- time_diff = current_time - last_summary_time_obj
+ # 如果 Redis 返回值为字节类型,转换为字符串
+ if last_summary_time:
+ last_summary_time = last_summary_time.decode('utf-8')
- if time_diff < timedelta(hours=3):
- # 如果小于 3 小时,取当天的内容
- last_summary_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0).strftime(
- '%Y-%m-%d %H:%M:%S')
- elif time_diff > timedelta(days=1):
- # 如果超过 24 小时,将时间设置为当天 0 点
- last_summary_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0).strftime(
- '%Y-%m-%d %H:%M:%S')
+ current_time = datetime.now()
+ current_date = current_time.strftime('%Y-%m-%d %H:%M:%S')
- # 更新 Redis 存储的当前时间
- # r.set(key, current_date)
+ if not last_summary_time:
+ # 获取当前时间并计算 8 小时前的时间
+ eight_hours_ago = current_time - timedelta(hours=8)
+ last_summary_time = eight_hours_ago.strftime('%Y-%m-%d %H:%M:%S')
+ else:
+ # 检查 redis 中的时间与当前时间差是否小于 3 小时
+ last_summary_time_obj = datetime.strptime(last_summary_time, '%Y-%m-%d %H:%M:%S')
+ time_diff = current_time - last_summary_time_obj
- with connection.cursor() as cursor:
- # 执行查询,获取最近 8 小时的消息
- query = """
- SELECT timestamp, sender, content,message_type
- FROM messages
- WHERE timestamp >= %s AND message_type in(1,49) AND group_id = %s
- """
- cursor.execute(query, (last_summary_time, group_id))
+ if time_diff < timedelta(hours=3):
+ # 如果小于 3 小时,取当天的内容
+ last_summary_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0).strftime(
+ '%Y-%m-%d %H:%M:%S')
+ elif time_diff > timedelta(days=1):
+ # 如果超过 24 小时,将时间设置为当天 0 点
+ last_summary_time = current_time.replace(hour=0, minute=0, second=0, microsecond=0).strftime(
+ '%Y-%m-%d %H:%M:%S')
- # 构建最终的结果字符串
- # message_type 需要加入49类型,因为49是引用之后的发言。但是49是xml ,需要将content进行xml解析
+ # 更新 Redis 存储的当前时间
+ # r.set(key, current_date)
- result = []
- for row in cursor.fetchall():
- timestamp, sender, content, message_type = row
- try:
- if message_type == "49":
- # 解析 XML 字符串
- root = ET.fromstring(content)
- # 提取 title 内容
- content = root.find('.//title').text
+ with connection.cursor() as cursor:
+ # 执行查询,获取最近 8 小时的消息
+ query = """
+ SELECT timestamp, sender, content,message_type
+ FROM messages
+ WHERE timestamp >= %s AND message_type in(1,49) AND group_id = %s
+ """
+ cursor.execute(query, (last_summary_time, group_id))
- except Exception as e:
- print(f"message_type 49 error: {e}")
- sender_name = all_contacts.get(sender, sender) # 获取发送者的名字,若找不到则使用原 ID
- result.append(f"{timestamp},{sender_name},{content}")
+ # 构建最终的结果字符串
+ # message_type 需要加入49类型,因为49是引用之后的发言。但是49是xml ,需要将content进行xml解析
- result_str = "\n".join(result) # 将结果拼接为最终字符串
- print(result_str)
- return result_str
+ result = []
+ for row in cursor.fetchall():
+ timestamp, sender, content, message_type = row
+ try:
+ if message_type == "49":
+ # 解析 XML 字符串
+ root = ET.fromstring(content)
+ # 提取 title 内容
+ content = root.find('.//title').text
+ except Exception as e:
+ print(f"message_type 49 error: {e}")
+ sender_name = all_contacts.get(sender, sender) # 获取发送者的名字,若找不到则使用原 ID
+ result.append(f"{timestamp},{sender_name},{content}")
-# 示例用法
-if __name__ == "__main__":
- # group_id = 'XXX@123123'
- # timestamp_str = "2025-02-06 11:15:28"
- # sender = "XXX"
- # content = "This is a test message with a string timestamp."
- # message_type = "text"
- # attachment_url = "http://example.com/attachment.pdf" # 可以为None如果没有附件
- #
- # archive_message(group_id, timestamp_str, sender, content, message_type, attachment_url)
- # get_messages("45317011307@chatroom", {})
- message_summary(get_messages("45317011307@chatroom", {}))
+ result_str = "\n".join(result) # 将结果拼接为最终字符串
+ print(result_str)
+ return result_str
diff --git a/music/bot_music.py b/music/bot_music.py
new file mode 100644
index 0000000..ff6c3ca
--- /dev/null
+++ b/music/bot_music.py
@@ -0,0 +1,63 @@
+import logging
+import tomllib
+
+import aiohttp
+from wcferry import WxMsg, Wcf
+
+from robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
+
+
+class BotMusic:
+ def __init__(self, wcf: Wcf, gbm: GroupBotManager):
+ self.LOG = logging.getLogger(__name__)
+ self.wcf = wcf # 假设 wcf 对象在此类中初始化
+ self.gbm = gbm # 权限功能
+ with open("music/config.toml", "rb") as f:
+ plugin_config = tomllib.load(f)
+
+ config = plugin_config["Music"]
+
+ self.enable = config["enable"]
+ self.command = config["command"]
+ self.command_format = config["command-format"]
+ self.LOG.info(f"[点歌台] 组件初始化完成,指令: {self.command}")
+
+ async def get_music(self, message: WxMsg):
+ if not self.enable:
+ return
+
+ content = str(message.content).strip()
+ command = content.split(" ")
+
+ if command[0] not in self.command:
+ return
+
+ if len(command) == 1:
+ self.wcf.send_text(f"-----Bot-----\n❌命令格式错误!{self.command_format}",
+ (message.roomid if message.from_group() else message.sender), message.sender)
+ return
+
+ # 如果触发了指令,但是没有权限,则返回权限不足
+ if self.gbm.get_group_permission(message.roomid, Feature.TASK_GAME) == PermissionStatus.DISABLED:
+ return
+
+ song_name = content[len(command[0]):].strip()
+
+ async with aiohttp.ClientSession() as session:
+ async with session.get(
+ f"https://www.hhlqilongzhu.cn/api/dg_wyymusic.php?gm={song_name}&n=1&br=2&type=json") as resp:
+ data = await resp.json()
+
+ if data["code"] != 200:
+ self.wcf.send_text(f"-----Bot-----\n❌点歌失败!\n{data}",
+ (message.roomid if message.from_group() else message.sender), message.sender)
+ return
+ title = data["title"]
+ singer = data["singer"]
+ url = data["link"]
+ music_url = data["music_url"].split("?")[0]
+ cover_url = data["cover"]
+ lyric = data["lrc"]
+
+ xml = f"""{title}{singer}view30{url}{music_url}{url}{music_url}{cover_url}{lyric}000{cover_url}{bot.wxid}01"""
+ self.wcf.send_xml(message.sender, xml, 3)
diff --git a/music/config.toml b/music/config.toml
new file mode 100644
index 0000000..50adc7b
--- /dev/null
+++ b/music/config.toml
@@ -0,0 +1,8 @@
+[Music]
+enable = true
+command = ["点歌", "音乐", "音乐点播", "点播音乐", "音乐点歌"]
+command-format = """
+-----Bot-----
+🎵点歌指令:
+点歌 歌曲名
+"""
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 69299e3..461bf64 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -26,4 +26,8 @@ websocket-client~=1.8.0
selenium~=4.28.1
webdriver-manager~=4.0.2
reportlab~=4.3.0
-PyPDF2~=3.0.1
\ No newline at end of file
+PyPDF2~=3.0.1
+Flask~=3.1.0
+aiohttp~=3.11.12
+mysql-connector-python~=9.2.0
+pytz~=2025.1
\ No newline at end of file
diff --git a/robot.py b/robot.py
index f173792..048b61b 100644
--- a/robot.py
+++ b/robot.py
@@ -9,6 +9,8 @@ from threading import Thread
from datetime import datetime, timedelta
import random
+import redis
+
from base.func_doubao import Doubao
from base.func_epic import is_friday, get_free
from base.func_zhipu import ZhiPu
@@ -29,16 +31,19 @@ from game_task.game_task_encyclopedia import game_process_message, setup_schedul
run_random_task_assignment
from group_auto.group_auto_invite import get_first_group_id, process_command
from group_auto.group_member_change import GroupMemberChange
+from message_sign.main import SignInSystem
+from message_storage.message_to_db import MessageStorage
+from music.bot_music import BotMusic
from robot_cmd.robot_command import GroupBotManager
from job_mgmt import Job
from robot_cmd.robot_command import Feature
from robot_cmd.robot_command import PermissionStatus
+import mysql.connector.pooling
__version__ = "39.2.4.0"
from message_report.process_message import process_message
from message_report.write_db import write_to_db, generate_and_send_ranking
-from message_storage.message_to_db import archive_message, get_messages
from message_summary.message_summary_4o import message_summary
from sehuatang.shehuatang import pdf_file_path
from xiuren.meitu_dl import meitu_dowload_pic, meitu_dowload_pub_pic, meitu_dowload_heisi_pic
@@ -55,11 +60,27 @@ class Robot(Job):
self.config = config
self.LOG = logging.getLogger("Robot")
self.wxid = self.wcf.get_self_wxid()
- self.allContacts = self.getAllContacts()
+ self.allContacts = self.get_all_contacts()
+ self.LOG.info(f"DB+REDIS 连接池开始初始化")
+ # db 配置加载
+ self.db_pool = mysql.connector.pooling.MySQLConnectionPool(self.config.mariadb)
+ self.LOG.info(f"DB连接池加载完成: {self.config.mariadb}")
+ self.redis_pool = redis.ConnectionPool(self.config.redis)
+ self.LOG.info(f"REDIS连接池加载完成: {self.config.redis}")
+
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
GroupBotManager.load_local_cache()
+ # 消息存档模块初始化,自动完成入库动作
+ self.message_storage = MessageStorage(self.db_pool, self.redis_pool)
+ # 权限模块加载
self.gbm = GroupBotManager()
- self.gmc = GroupMemberChange(wcf)
+ # 群成员变更模块加载
+ self.gmc = GroupMemberChange(wcf, self.redis_pool)
+ # 点歌模块加载
+ self.music = BotMusic(wcf, self.gbm)
+ # 签到模块加载
+ self.signin = SignInSystem(wcf, self.gbm, self.allContacts, self.db_pool, self.redis_pool)
+
if ChatType.is_in_chat_types(chat_type):
if chat_type == ChatType.TIGER_BOT.value and TigerBot.value_check(self.config.TIGERBOT):
self.chat = TigerBot(self.config.TIGERBOT)
@@ -132,13 +153,13 @@ class Robot(Job):
if cy.isChengyu(text):
rsp = cy.getNext(text)
if rsp:
- self.sendTextMsg(rsp, msg.roomid)
+ self.send_text_msg(rsp, msg.roomid)
status = True
elif flag in ["?", "?"]: # 查词
if cy.isChengyu(text):
rsp = cy.getMeaning(text)
if rsp:
- self.sendTextMsg(rsp, msg.roomid)
+ self.send_text_msg(rsp, msg.roomid)
status = True
return status
@@ -172,15 +193,15 @@ class Robot(Job):
player_id = resp["player_id"]
print(f"消息: {message}")
print(f"玩家ID: {player_id}")
- self.sendTextMsg(message, msg.roomid, msg.sender)
+ self.send_text_msg(message, msg.roomid, msg.sender)
except Exception as e:
self.LOG.error(f"game_message_load error:{e}")
return True
if q == "#今日百度新闻":
- self.newsBaiduReport((msg.roomid if msg.from_group() else msg.sender))
+ self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
return True
elif q in ["nbc", "cnn", "abc", "fox", "bbc"]:
- self.newsEnReport(q, (msg.roomid if msg.from_group() else msg.sender))
+ self.news_en_report(q, (msg.roomid if msg.from_group() else msg.sender))
return True
elif q == '#总结':
self.message_summary_robot((msg.roomid if msg.from_group() else msg.sender))
@@ -221,7 +242,7 @@ class Robot(Job):
# 如果是群消息,并且群没开启AI,则不处理该动作
if msg.from_group() and self.gbm.get_group_permission(msg.roomid,
Feature.AI_CAPABILITY) == PermissionStatus.DISABLED:
- self.sendTextMsg("群AI功能未开启", msg.roomid, msg.sender)
+ self.send_text_msg("群AI功能未开启", msg.roomid, msg.sender)
return True
else:
if msg.type == 1: # 只处理类型为1的消息提供的问题,引用@不予以对话
@@ -230,9 +251,9 @@ class Robot(Job):
return True
if rsp:
if msg.from_group():
- self.sendTextMsg(rsp, msg.roomid, msg.sender)
+ self.send_text_msg(rsp, msg.roomid, msg.sender)
else:
- self.sendTextMsg(rsp, msg.sender)
+ self.send_text_msg(rsp, msg.sender)
return True
else:
@@ -259,7 +280,7 @@ class Robot(Job):
# 聊天记录入库动作:
try:
now_time = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
- archive_message(msg.roomid, now_time, msg.sender, msg.content, msg.type, msg.extra)
+ self.message_storage.archive_message(msg.roomid, now_time, msg.sender, msg.content, msg.type, msg.extra)
except Exception as e:
self.LOG.error(f"archive_message error: {e}")
@@ -268,7 +289,7 @@ class Robot(Job):
if msg.from_self():
self.revoke_receive_message(msg)
rsp = self.gbm.handle_command(msg.roomid, msg.content)
- self.sendTextMsg(rsp, msg.roomid, msg.sender)
+ self.send_text_msg(rsp, msg.roomid, msg.sender)
return
except Exception as e:
self.LOG.error(f"revoke_receive_message error: {e}")
@@ -288,7 +309,7 @@ class Robot(Job):
player_id = resp["player_id"]
print(f"消息: {message}")
print(f"玩家ID: {player_id}")
- self.sendTextMsg(message, msg.roomid, msg.sender)
+ self.send_text_msg(message, msg.roomid, msg.sender)
return
except Exception as e:
self.LOG.error(f"game_message_load error:{e}")
@@ -298,25 +319,25 @@ class Robot(Job):
# 判断是否没有变化
if "$NO_CHANGE$" not in result:
self.LOG.info(f"检测到群成员变化,进行相关内容输出:{result}")
- self.sendTextMsg(result, msg.roomid)
+ self.send_text_msg(result, msg.roomid)
except Exception as e:
self.LOG.error(f"group_member_change error: {e}")
+ try:
+ self.music.get_music(message=msg)
+ except Exception as e:
+ self.LOG.error(f"get_music error: {e}")
if msg.is_at(self.wxid): # 被@
self.toAt(msg)
-
- else: # 其他消息
- self.toChengyu(msg)
-
return # 处理完群聊信息,后面就不需要处理了
# 非群聊信息,按消息类型进行处理
if msg.type == 37: # 好友请求
self.LOG.info(f"收到好友请求:{msg}")
- self.autoAcceptFriendRequest(msg)
+ self.auto_accept_friend_request(msg)
elif msg.type == 10000: # 系统信息
- self.sayHiToNewFriend(msg)
+ self.say_hi_to_new_friend(msg)
elif msg.type == 0x01: # 文本消息
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
@@ -326,15 +347,15 @@ class Robot(Job):
self.gbm.load_local_cache()
self.LOG.info("已更新")
if msg.content == "今日百度新闻":
- self.newsBaiduReport()
+ self.news_baidu_report()
if msg.content == '聊天排行榜':
- self.generateAndSendRanking()
+ self.generate_and_send_ranking()
if msg.content == '聊天数据入库':
- self.messageCountToDB()
+ self.message_count_to_db()
if msg.content == 'PDF':
- self.generateSehuatangPdf()
+ self.generate_sehuatang_pdf()
if msg.content == 'GROUP_LIST':
- self.sendTextMsg(self.gbm.get_group_list(), msg.sender)
+ self.send_text_msg(self.gbm.get_group_list(), msg.sender)
if msg.content.startswith('#加群配置'):
# msg_content = "# 加群配置|add 原生鱼 xxx@room"
parts = msg.content.split('|')
@@ -344,8 +365,8 @@ class Robot(Job):
resp = process_command(after_pipe)
else:
resp = process_command("help")
- self.sendTextMsg(resp, msg.sender)
- self.sendTextMsg(f"指令:{msg.content} 已执行", msg.sender)
+ self.send_text_msg(resp, msg.sender)
+ self.send_text_msg(f"指令:{msg.content} 已执行", msg.sender)
else:
self.toChitchat(msg) # 闲聊
@@ -376,7 +397,7 @@ class Robot(Job):
self.wcf.enable_receiving_msg()
Thread(target=innerProcessMsg, name="GetMessage", args=(self.wcf,), daemon=True).start()
- def sendTextMsg(self, msg: str, receiver: str, at_list: str = "") -> None:
+ def send_text_msg(self, msg: str, receiver: str, at_list: str = "") -> None:
""" 发送消息
:param msg: 消息字符串
:param receiver: 接收人wxid或者群id
@@ -405,7 +426,7 @@ class Robot(Job):
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
- def getAllContacts(self) -> dict:
+ def get_all_contacts(self) -> dict:
"""
获取联系人(包括好友、公众号、服务号、群成员……)
格式: {"wxid": "NickName"}
@@ -413,7 +434,7 @@ class Robot(Job):
contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
return {contact["UserName"]: contact["NickName"] for contact in contacts}
- def keepRunningAndBlockProcess(self) -> None:
+ def keep_running_and_block_process(self) -> None:
"""
保持机器人运行,不让进程退出
"""
@@ -421,7 +442,7 @@ class Robot(Job):
self.runPendingJobs()
time.sleep(1)
- def autoAcceptFriendRequest(self, msg: WxMsg) -> None:
+ def auto_accept_friend_request(self, msg: WxMsg) -> None:
try:
xml = ET.fromstring(msg.content)
v3 = xml.attrib["encryptusername"]
@@ -432,12 +453,12 @@ class Robot(Job):
except Exception as e:
self.LOG.error(f"同意好友出错:{e}")
- def sayHiToNewFriend(self, msg: WxMsg) -> None:
+ def say_hi_to_new_friend(self, msg: WxMsg) -> None:
nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content)
if nickName:
# 添加了好友,更新好友列表
self.allContacts[msg.sender] = nickName[0]
- self.sendTextMsg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
+ self.send_text_msg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
def send_group_txt_message(self, msg: str, feature: Feature):
try:
@@ -446,7 +467,7 @@ class Robot(Job):
return
for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
- self.sendTextMsg(msg, r)
+ self.send_text_msg(msg, r)
except Exception as e:
self.LOG.error(f"send_group_txt_message:{feature.description} error:{e}")
@@ -497,28 +518,28 @@ class Robot(Job):
# ============================================== 业务内容==========================================================
- def newsBaiduReportAuto(self) -> None:
+ def news_baidu_report_auto(self) -> None:
try:
news = News().get_baidu_news()
self.send_group_txt_message(news, Feature.DAILY_NEWS)
except Exception as e:
self.LOG.error(f"newsBaiduReportAuto error:{e}")
- def newsBaiduReport(self, sender: str = None) -> None:
+ def news_baidu_report(self, sender: str = None) -> None:
try:
news = News().get_baidu_news()
- self.sendTextMsg(news, sender)
+ self.send_text_msg(news, sender)
except Exception as e:
self.LOG.error(f"newsBaiduReport error:{e}")
- def newsEnReport(self, website, sender: str = None) -> None:
+ def news_en_report(self, website, sender: str = None) -> None:
try:
news = News().get_eng_news(website)
- self.sendTextMsg(news, sender)
+ self.send_text_msg(news, sender)
except Exception as e:
self.LOG.error(f"newsEnReport error:{e}")
- def sendEpicFreeGames(self):
+ def send_epic_free_games(self):
try:
if is_friday():
games = get_free()
@@ -526,13 +547,13 @@ class Robot(Job):
except Exception as e:
self.LOG.error(f"sendEpicFreeGames error:{e}")
- def messageCountToDB(self):
+ def message_count_to_db(self):
try:
write_to_db()
except Exception as e:
self.LOG.error(f"write_to_db error:{e}")
- def generateSehuatangPdf(self):
+ def generate_sehuatang_pdf(self):
try:
path = pdf_file_path()
# 暂时只发4K群
@@ -540,14 +561,14 @@ class Robot(Job):
except Exception as e:
self.LOG.error(f"generateSehuatangPdf error:{e}")
- def generateAndSendRanking(self):
+ def generate_and_send_ranking(self):
try:
receivers = self.gbm.get_group_list()
if not receivers:
return
for r in receivers:
if self.gbm.get_group_permission(r, Feature.DAILY_SUMMARY) == PermissionStatus.ENABLED:
- self.sendTextMsg(generate_and_send_ranking(r, self.allContacts), r)
+ self.send_text_msg(generate_and_send_ranking(r, self.allContacts), r)
except Exception as e:
self.LOG.error(f"SendRanking error:{e}")
@@ -555,11 +576,11 @@ class Robot(Job):
try:
if self.gbm.get_group_permission(sender, Feature.SUMMARY_CAPABILITY) == PermissionStatus.ENABLED:
self.LOG.info(f"群: {sender} 消息总结开始执行!")
- content = get_messages(sender, self.allContacts)
+ content = self.message_storage.get_messages(sender, self.allContacts)
summary = message_summary(content)
- self.sendTextMsg(summary, sender)
+ self.send_text_msg(summary, sender)
else:
- self.sendTextMsg("群发言总结功能未开启", sender)
+ self.send_text_msg("群发言总结功能未开启", sender)
except Exception as e:
self.LOG.error(f"message_summary_robot error:{e}")
@@ -574,7 +595,7 @@ class Robot(Job):
player_id = rep["player_id"]
print(f"消息: {message}")
print(f"玩家ID: {player_id}")
- self.sendTextMsg(message, gid, player_id)
+ self.send_text_msg(message, gid, player_id)
except Exception as e:
self.LOG.error(f"message_summary_robot error:{e}")
diff --git a/robot_cmd/robot_command.py b/robot_cmd/robot_command.py
index b1a0c2e..fbde5aa 100644
--- a/robot_cmd/robot_command.py
+++ b/robot_cmd/robot_command.py
@@ -31,6 +31,7 @@ class Feature(Enum):
EPIC = 7, "EPIC自动播报" # 新增的功能
PIC = 8, "图来能力"
TASK_GAME = 9, "百科答题游戏"
+ MUSIC = 10, "点歌功能"
def __new__(cls, value, description):
obj = object.__new__(cls)