Skip to content

Commit e2f79c2

Browse files
authored
Merge pull request #9 from nuist-engineer-lion:dev
feat: 根据成员可用时间段筛选候选人,并在提醒时随机 @ 当前可接单的人
2 parents 6a4de8b + 6aa0619 commit e2f79c2

1 file changed

Lines changed: 155 additions & 31 deletions

File tree

main.py

Lines changed: 155 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import asyncio
33
import logging
44
import statistics
5+
import random
6+
from datetime import datetime
57
from collections import deque
68
from typing import Any, TypedDict
79

@@ -11,13 +13,14 @@
1113
FriendRequestEvent,
1214
GroupMsgEmojiLikeEvent,
1315
FriendPokeEvent,
14-
GroupMessageEvent,
15-
GroupPokeEvent,
16+
GroupMessageEvent,
17+
GroupPokeEvent,
1618
Message,
1719
NodeInline,
1820
NodeReference,
1921
Reply,
2022
Text,
23+
At,
2124
)
2225

2326
# ================= 日志配置 =================
@@ -40,7 +43,7 @@
4043
# ================= 程序启动时间 =================
4144
STARTED_AT = time.time()
4245

43-
# 防抖间隔(秒),可按需调整或分别为每种指令设置不同值
46+
# 防抖间隔(秒)
4447
DEBOUNCE_SECONDS = {
4548
"say": 5,
4649
"more": 5,
@@ -49,16 +52,97 @@
4952
# 好友申请去重缓存(flag -> 处理时间戳)
5053
PROCESSED_FRIEND_REQUESTS_EXPIRE = 60 # 缓存保留 60 秒
5154

55+
# ================= 成员可用时间段配置 =================
56+
# 格式:{ QQ号: { 星期几英文: [(开始时间, 结束时间), ...] } }
57+
# 星期几可用 "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"
58+
AVAILABILITY: dict[int, dict[str, list[tuple[str, str]]]] = {
59+
234666220: { # yjx
60+
"monday": [("00:00", "11:59"), ("12:00", "23:59")],
61+
"tuesday": [],
62+
"wednesday": [],
63+
"thursday": [],
64+
"friday": [],
65+
"saturday": [],
66+
"sunday": [],
67+
},
68+
1284656930: { # cz
69+
"monday": [],
70+
"tuesday": [("00:00", "11:59"), ("12:00", "23:59")],
71+
"wednesday": [],
72+
"thursday": [],
73+
"friday": [],
74+
"saturday": [],
75+
"sunday": [],
76+
},
77+
3499737435: { # hyn
78+
"monday": [],
79+
"tuesday": [],
80+
"wednesday": [("00:00", "11:59"), ("12:00", "23:59")],
81+
"thursday": [],
82+
"friday": [],
83+
"saturday": [],
84+
"sunday": [],
85+
},
86+
3525362286: { # djh
87+
"monday": [],
88+
"tuesday": [],
89+
"wednesday": [],
90+
"thursday": [("00:00", "11:59"), ("12:00", "23:59")],
91+
"friday": [],
92+
"saturday": [],
93+
"sunday": [],
94+
},
95+
1372438873: { # ljh
96+
"monday": [],
97+
"tuesday": [],
98+
"wednesday": [],
99+
"thursday": [],
100+
"friday": [("00:00", "11:59"), ("12:00", "23:59")],
101+
"saturday": [],
102+
"sunday": [],
103+
},
104+
3026425376: { # pwy
105+
"monday": [],
106+
"tuesday": [],
107+
"wednesday": [],
108+
"thursday": [],
109+
"friday": [],
110+
"saturday": [("00:00", "11:59"), ("12:00", "23:59")],
111+
"sunday": [],
112+
},
113+
2018160654: { # yyx
114+
"monday": [],
115+
"tuesday": [],
116+
"wednesday": [],
117+
"thursday": [],
118+
"friday": [],
119+
"saturday": [],
120+
"sunday": [("00:00", "11:59"), ("12:00", "23:59")],
121+
}
122+
}
123+
124+
# 星期几数字到英文的映射(datetime.weekday() 返回 0=周一, 6=周日)
125+
WEEKDAY_MAP = {
126+
0: "monday",
127+
1: "tuesday",
128+
2: "wednesday",
129+
3: "thursday",
130+
4: "friday",
131+
5: "saturday",
132+
6: "sunday",
133+
}
134+
# =================================================
135+
52136
# ================= 回复耗时记录(秒) =================
53-
REPLY_DURATION_MAXLEN = 1000 # 保留最近 N 次回复耗时,防止列表无限增长
137+
REPLY_DURATION_MAXLEN = 1000 # 保留最近 N 次回复耗时
54138
reply_durations: deque[float] = deque(maxlen=REPLY_DURATION_MAXLEN)
55139

56140
class CustomerData(TypedDict):
57141
last_active: float
58142
msg_ids: list[int]
59143
is_newly_reported: bool
60144
reported_milestones: set[int]
61-
pending_since: float # 本轮开始等待回复的时间戳
145+
pending_since: float # 本轮开始等待回复的时间戳
62146

63147

64148
class ForwardMonitorData(TypedDict):
@@ -70,8 +154,8 @@ class ForwardMonitorData(TypedDict):
70154
unreplied_customers: dict[int, CustomerData] = {}
71155
monitored_forward_order: deque[int] = deque()
72156
monitored_forwards: dict[int, ForwardMonitorData] = {}
73-
last_command_time: dict[tuple[int, str], float] = {} # 记录每条消息上各指令的最后执行时间,键为 (message_id, command_type)
74-
processed_friend_requests: dict[str, float] = {} # 好友申请去重缓存(flag -> 处理时间戳)
157+
last_command_time: dict[tuple[int, str], float] = {} # 记录每条消息上各指令的最后执行时间
158+
processed_friend_requests: dict[str, float] = {} # 好友申请去重缓存
75159

76160
# 客户端对象
77161
client: NapCatClient = NapCatClient(WS_URL, WS_TOKEN)
@@ -121,9 +205,10 @@ def pop_tracked_forward(message_id: int) -> ForwardMonitorData | None:
121205

122206
return data
123207

208+
124209
# ================= 格式化时长 =================
125210
def format_duration(seconds: float) -> str:
126-
"""将秒数转换为易读的文本(如 3分12秒、1小时05分)"""
211+
"""将秒数转换为易读的文本"""
127212
if seconds < 60:
128213
return f"{int(seconds)}秒"
129214
elif seconds < 3600:
@@ -228,11 +313,6 @@ async def send_nested_forward(group_id: int, customer_list: list[tuple[int, Cust
228313

229314
log.info("开始构造合并转发 -> 群 %d, 共 %d 名客户", group_id, len(customer_list))
230315
outer_nodes: list[Message] = [
231-
NodeInline(
232-
nickname="客服系统提示",
233-
user_id=str(client.self_id),
234-
content=serialize_message_segments([Text(text=summary_text)]),
235-
),
236316
NodeInline(
237317
nickname="快捷传送门",
238318
user_id=str(client.self_id),
@@ -270,6 +350,62 @@ async def send_nested_forward(group_id: int, customer_list: list[tuple[int, Cust
270350
log.error("发送合并转发失败: %s", e, exc_info=True)
271351
return None
272352

353+
354+
# ================= 新增:获取当前可用成员 =================
355+
def get_current_available_members() -> list[int]:
356+
"""根据当前系统时间,返回所有可用的成员QQ列表"""
357+
now = datetime.now()
358+
weekday_name = WEEKDAY_MAP[now.weekday()]
359+
current_time = now.time()
360+
361+
available: list[int] = [] # 明确类型为整数列表
362+
for qq, schedule in AVAILABILITY.items():
363+
time_slots = schedule.get(weekday_name, [])
364+
for start_str, end_str in time_slots:
365+
try:
366+
start = datetime.strptime(start_str, "%H:%M").time()
367+
end = datetime.strptime(end_str, "%H:%M").time()
368+
if start <= current_time <= end:
369+
available.append(qq) # 现在类型检查器知道 qq 是 int
370+
break
371+
except Exception as e:
372+
log.error("解析时间段失败: qq=%s, slot=%s-%s, err=%s", qq, start_str, end_str, e)
373+
continue
374+
return available
375+
376+
377+
# ================= 新增:发送带 @ 的提醒 =================
378+
async def send_reminder_with_at(group_id: int, summary: str, customer_list: list[tuple[int, CustomerData]]) -> int | None:
379+
"""
380+
发送提醒:先尝试 @ 一位当前可用成员,再发送合并转发。
381+
若无可用成员,则直接发送合并转发(无 @)。
382+
"""
383+
available = get_current_available_members()
384+
if available:
385+
chosen = random.choice(available)
386+
# 构造 @ 消息,使用库提供的消息段类
387+
at_msg: list[Message] = [
388+
At(qq=str(chosen)), # 假设 At 接受 qq 参数
389+
Text(text=f" {summary}"),
390+
]
391+
try:
392+
await client.send_group_msg(group_id=str(group_id), message=at_msg)
393+
log.info("已发送 @%d 提醒: %s", chosen, summary)
394+
except Exception as e:
395+
log.error("发送 @ 提醒失败: %s", e, exc_info=True)
396+
else:
397+
log.debug("当前无可用成员,直接发送合并转发")
398+
399+
# 发送合并转发
400+
message_id = await send_nested_forward(group_id, customer_list, summary)
401+
if message_id is not None:
402+
track_forward_message(
403+
message_id=message_id,
404+
customer_ids=[qq for qq, _ in customer_list],
405+
group_id=group_id,
406+
)
407+
return message_id
408+
273409
# ================= 核心逻辑:定时巡检任务 =================
274410
async def monitor_loop():
275411
"""每分钟执行一次的巡检死循环"""
@@ -326,13 +462,8 @@ async def monitor_loop():
326462

327463
# 只通报新触发的客户,避免每次都全量刷屏
328464
summary = f"📢 刚刚有 {len(new_customers_to_report)} 名客户发来消息,请及时回复!"
329-
message_id = await send_nested_forward(INTERNAL_GROUP_ID, new_customers_to_report, summary)
330-
if message_id is not None:
331-
track_forward_message(
332-
message_id=message_id,
333-
customer_ids=[qq for qq, _ in new_customers_to_report],
334-
group_id=INTERNAL_GROUP_ID,
335-
)
465+
# 替换为带 @ 的提醒
466+
await send_reminder_with_at(INTERNAL_GROUP_ID, summary, new_customers_to_report)
336467
else:
337468
log.debug("阶段1: 无新客户需要通报")
338469

@@ -364,13 +495,8 @@ async def monitor_loop():
364495
log.warning("阶段2: 里程碑 %s 触发, 涉及 %d 名客户: %s",
365496
unit, len(delay_list), [qq for qq, _ in delay_list])
366497
summary = f"⚠️ 以下 {len(delay_list)} 名客户已等待长达 {unit}!"
367-
message_id = await send_nested_forward(INTERNAL_GROUP_ID, delay_list, summary)
368-
if message_id is not None:
369-
track_forward_message(
370-
message_id=message_id,
371-
customer_ids=[qq for qq, _ in delay_list],
372-
group_id=INTERNAL_GROUP_ID,
373-
)
498+
# 替换为带 @ 的提醒
499+
await send_reminder_with_at(INTERNAL_GROUP_ID, summary, delay_list)
374500

375501
log.info("===== 巡检结束 =====")
376502

@@ -452,9 +578,8 @@ async def main():
452578
log.debug("收到事件: type=%s, post_type=%s", type(event).__name__, getattr(event, 'post_type', '?'))
453579
match event:
454580
# 0. 自动通过好友申请
455-
case FriendRequestEvent(user_id=uid, comment=comment,flag=flag):
456-
log.info("收到好友申请:"+str(uid)+" "+comment)
457-
# 去重检查:如果同一个 flag 在短时间内已处理过,则跳过
581+
case FriendRequestEvent(user_id=uid, comment=comment, flag=flag):
582+
log.info("收到好友申请:" + str(uid) + " " + comment)
458583
now = time.time()
459584
last_time = processed_friend_requests.get(flag)
460585
if last_time and (now - last_time) < PROCESSED_FRIEND_REQUESTS_EXPIRE:
@@ -559,7 +684,6 @@ async def main():
559684
if closed:
560685
log.info("私聊戳一戳结束会话: user_id=%s", sid)
561686
else:
562-
# 客户不在队列中,但已发送结束语
563687
log.info("私聊戳一戳自动发送结束语(客户不在队列): user_id=%s", sid)
564688

565689
# 5. 内部群戳一戳 -> 发送状态面板

0 commit comments

Comments
 (0)