增强社交统计并扩展 value_rank 社交榜单命令
- 在消息入库增量链路中回填 unique_interactors,实现去重互动人数实时更新 - 新增社交热度榜命令:社交热度榜 [名次],按互动分/被@/主动@展示 - 新增搭子榜命令:搭子榜 [名次],按无向关系边聚合展示成员组合 - 扩展 ValueRankDB 查询接口,支持社交热度与搭子关系窗口聚合 - 同步更新 value_rank 配置命令列表与帮助文案
This commit is contained in:
@@ -258,10 +258,63 @@ class MessageStorageDB(BaseDBOperator):
|
||||
""",
|
||||
receiver_social_rows,
|
||||
)
|
||||
|
||||
# 4) 回填 unique_interactors:针对本条消息受影响的用户实时重算“去重互动人数”。
|
||||
affected_user_ids = [sender_id, *clean_mentioned_ids]
|
||||
self._refresh_unique_interactors(stat_date, group_id, affected_user_ids)
|
||||
except Exception as e:
|
||||
# 社交图统计属于增强链路,不能反向影响主消息入库稳定性。
|
||||
self.LOG.error(f"写入社交图增量数据失败: {e}")
|
||||
|
||||
def _refresh_unique_interactors(self, stat_date: str, group_id: str, user_ids: List[str]) -> None:
|
||||
"""重算并回填用户在指定日期内的去重互动人数。
|
||||
|
||||
定义:
|
||||
- 某用户当天主动@过的人 + 被谁@过(去重并集)
|
||||
"""
|
||||
if not user_ids:
|
||||
return
|
||||
|
||||
deduped_user_ids = []
|
||||
seen = set()
|
||||
for uid in user_ids:
|
||||
normalized_uid = str(uid or "").strip()
|
||||
if not normalized_uid or normalized_uid in seen:
|
||||
continue
|
||||
seen.add(normalized_uid)
|
||||
deduped_user_ids.append(normalized_uid)
|
||||
|
||||
for uid in deduped_user_ids:
|
||||
try:
|
||||
row = self.execute_query(
|
||||
"""
|
||||
SELECT COUNT(DISTINCT partner_id) AS partner_count
|
||||
FROM (
|
||||
SELECT mentioned_user_id AS partner_id
|
||||
FROM t_message_mentions
|
||||
WHERE stat_date = %s AND group_id = %s AND sender_id = %s
|
||||
UNION
|
||||
SELECT sender_id AS partner_id
|
||||
FROM t_message_mentions
|
||||
WHERE stat_date = %s AND group_id = %s AND mentioned_user_id = %s
|
||||
) t
|
||||
""",
|
||||
(stat_date, group_id, uid, stat_date, group_id, uid),
|
||||
fetch_one=True,
|
||||
) or {}
|
||||
partner_count = int(row.get("partner_count") or 0)
|
||||
|
||||
self.execute_update(
|
||||
"""
|
||||
UPDATE t_value_rank_social_daily
|
||||
SET unique_interactors = %s, update_time = CURRENT_TIMESTAMP
|
||||
WHERE stat_date = %s AND group_id = %s AND user_id = %s
|
||||
""",
|
||||
(partner_count, stat_date, group_id, uid),
|
||||
)
|
||||
except Exception as e:
|
||||
self.LOG.error(f"回填 unique_interactors 失败: group={group_id}, user={uid}, err={e}")
|
||||
|
||||
def get_recent_messages(self, group_id: str, hours_ago: int = 8, min_content_length: int = 6) -> List[Dict]:
|
||||
"""获取最近的消息"""
|
||||
sql = """
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
[ValueRank]
|
||||
enable = true
|
||||
command = ["我的身价", "身价排行", "身价说明", "重算身价"]
|
||||
command = ["我的身价", "身价排行", "社交热度榜", "搭子榜", "身价说明", "重算身价"]
|
||||
command-format = """
|
||||
📊 身价系统命令:
|
||||
1. 我的身价
|
||||
2. 身价排行 [名次]
|
||||
3. 身价说明
|
||||
4. 重算身价(管理员)
|
||||
3. 社交热度榜 [名次]
|
||||
4. 搭子榜 [名次]
|
||||
5. 身价说明
|
||||
6. 重算身价(管理员)
|
||||
"""
|
||||
|
||||
# 统计窗口(天)
|
||||
|
||||
@@ -216,6 +216,41 @@ class ValueRankDB(BaseDBOperator):
|
||||
"""
|
||||
return self.execute_query(sql, (stat_date, group_id, limit)) or []
|
||||
|
||||
def get_social_hot_rankings(self, group_id: str, social_window_days: int, limit: int) -> List[Dict[str, Any]]:
|
||||
"""读取社交热度榜(窗口聚合)。"""
|
||||
sql = """
|
||||
SELECT
|
||||
user_id,
|
||||
SUM(mentioned_count) AS mentioned_count_window,
|
||||
SUM(mention_others_count) AS mention_others_count_window,
|
||||
SUM(unique_interactors) AS unique_interactors_window,
|
||||
SUM(interaction_score) AS interaction_score_window
|
||||
FROM t_value_rank_social_daily
|
||||
WHERE group_id = %s
|
||||
AND stat_date >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
|
||||
GROUP BY user_id
|
||||
ORDER BY interaction_score_window DESC, mentioned_count_window DESC
|
||||
LIMIT %s
|
||||
"""
|
||||
return self.execute_query(sql, (group_id, social_window_days, limit)) or []
|
||||
|
||||
def get_top_partner_pairs(self, group_id: str, social_window_days: int, limit: int) -> List[Dict[str, Any]]:
|
||||
"""读取搭子榜(无向边聚合)。"""
|
||||
sql = """
|
||||
SELECT
|
||||
LEAST(from_user_id, to_user_id) AS user_a,
|
||||
GREATEST(from_user_id, to_user_id) AS user_b,
|
||||
SUM(mention_count) AS mention_count_window,
|
||||
SUM(interaction_score) AS interaction_score_window
|
||||
FROM t_social_edges_daily
|
||||
WHERE group_id = %s
|
||||
AND stat_date >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
|
||||
GROUP BY LEAST(from_user_id, to_user_id), GREATEST(from_user_id, to_user_id)
|
||||
ORDER BY interaction_score_window DESC, mention_count_window DESC
|
||||
LIMIT %s
|
||||
"""
|
||||
return self.execute_query(sql, (group_id, social_window_days, limit)) or []
|
||||
|
||||
|
||||
class ValueRankPlugin(MessagePluginInterface):
|
||||
"""群成员身价排行插件。
|
||||
@@ -268,7 +303,7 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
|
||||
# 配置默认值:即使未配置 config.toml,也能以保守参数运行。
|
||||
self.enable = True
|
||||
self._commands = ["我的身价", "身价排行", "身价说明", "重算身价"]
|
||||
self._commands = ["我的身价", "身价排行", "社交热度榜", "搭子榜", "身价说明", "重算身价"]
|
||||
self.command_format = "我的身价 | 身价排行 [名次] | 身价说明 | 重算身价"
|
||||
|
||||
self.message_window_days = 7
|
||||
@@ -369,6 +404,18 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
await bot.send_text_message(roomid, text, sender)
|
||||
return True, "查询成功"
|
||||
|
||||
if command == "社交热度榜":
|
||||
limit = self._parse_rank_limit(content)
|
||||
text = await self._build_social_hot_text(roomid, limit)
|
||||
await bot.send_text_message(roomid, text, sender)
|
||||
return True, "查询成功"
|
||||
|
||||
if command == "搭子榜":
|
||||
limit = self._parse_rank_limit(content)
|
||||
text = await self._build_partner_pairs_text(roomid, limit)
|
||||
await bot.send_text_message(roomid, text, sender)
|
||||
return True, "查询成功"
|
||||
|
||||
if command == "身价说明":
|
||||
await bot.send_text_message(roomid, self._build_explain_text(), sender)
|
||||
return True, "查询成功"
|
||||
@@ -648,6 +695,51 @@ class ValueRankPlugin(MessagePluginInterface):
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
async def _build_social_hot_text(self, group_id: str, limit: int) -> str:
|
||||
"""构建“社交热度榜”输出文本。"""
|
||||
if not self.db:
|
||||
return "❌ 身价模块未初始化"
|
||||
|
||||
rows = self.db.get_social_hot_rankings(group_id, self.social_window_days, limit)
|
||||
if not rows:
|
||||
return "📊 近期暂无社交热度数据。"
|
||||
|
||||
cm = ContactManager.get_instance()
|
||||
lines = [f"🔥 社交热度榜(近{self.social_window_days}天 Top{len(rows)})"]
|
||||
for idx, row in enumerate(rows, start=1):
|
||||
user_id = str(row.get("user_id") or "")
|
||||
score = float(row.get("interaction_score_window") or 0.0)
|
||||
mentioned_count = int(row.get("mentioned_count_window") or 0)
|
||||
mention_others_count = int(row.get("mention_others_count_window") or 0)
|
||||
nick = cm.get_group_name(group_id, user_id) or user_id
|
||||
medal = "🥇" if idx == 1 else "🥈" if idx == 2 else "🥉" if idx == 3 else f"{idx}."
|
||||
lines.append(
|
||||
f"{medal} {nick} | 热度{score:.1f} | 被@{mentioned_count} | 主动@{mention_others_count}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
async def _build_partner_pairs_text(self, group_id: str, limit: int) -> str:
|
||||
"""构建“搭子榜”输出文本。"""
|
||||
if not self.db:
|
||||
return "❌ 身价模块未初始化"
|
||||
|
||||
rows = self.db.get_top_partner_pairs(group_id, self.social_window_days, limit)
|
||||
if not rows:
|
||||
return "📊 近期暂无搭子关系数据。"
|
||||
|
||||
cm = ContactManager.get_instance()
|
||||
lines = [f"🤝 搭子榜(近{self.social_window_days}天 Top{len(rows)})"]
|
||||
for idx, row in enumerate(rows, start=1):
|
||||
user_a = str(row.get("user_a") or "")
|
||||
user_b = str(row.get("user_b") or "")
|
||||
nick_a = cm.get_group_name(group_id, user_a) or user_a
|
||||
nick_b = cm.get_group_name(group_id, user_b) or user_b
|
||||
mention_count = int(row.get("mention_count_window") or 0)
|
||||
score = float(row.get("interaction_score_window") or 0.0)
|
||||
medal = "🥇" if idx == 1 else "🥈" if idx == 2 else "🥉" if idx == 3 else f"{idx}."
|
||||
lines.append(f"{medal} {nick_a} × {nick_b} | 互动{score:.1f} | @次数{mention_count}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _build_explain_text(self) -> str:
|
||||
"""输出算法说明文本。"""
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user