o
    3Ypjn                     @   s  d dl Z d dlZd dlZd dlmZmZ d dlmZ d dlmZ d dl	m
Z
 d dlmZ ddlmZmZmZ dd	lmZ d
ededB fddZd
ededB fddZdedefddZdZdee dedeeB fddZdefddZdefddZdedefdd Z eG d!d" d"eZ!d#edee fd$d%Z"d&ed'ee d(ede#fd)d*Z$d&ed+ed'ee fd,d-Z%d&ed.efd/d0Z&d&efd1d2Z'd&ededB fd3d4Z(eG d5d6 d6eZ)dS )7    N)datetime	timedelta)Plugin)text)logger)register_plugin   )
build_ringrelay_assignmentscurrent_turn)archiverawreturnc              	   C   s8   dD ]}zt | |dW   S  ty   Y qw dS )u_   接受 'YYYY-MM-DD HH:MM' 或 'YYYY-MM-DD HH:MM:SS'，返回完整格式；非法返回 None。)%Y-%m-%d %H:%M:%Sz%Y-%m-%d %H:%Mr   N)r   strptimestrftime
ValueError)r   fmt r   6/home/dore/onebot/Bot_Ero/plugins/activity/__init__.py_parse_deadline   s   r   c                 C   sH   t d| }|s
dS t|d}|ddv r|d9 }|dkr"|S dS )uh   解析接龙每人时限：'48'/'48小时'/'2天'（也接受 h/d）→ 小时数；非法返回 None。u)   \s*(\d+(?:\.\d+)?)\s*(小时|天|h|d)?\s*Nr      )u   天d   r   )re	fullmatchfloatgroup)r   mhoursr   r   r   _parse_duration   s   r    r   c                 C   s*   | d dkrt | d  dS | ddS )u&   48 → '2 天'；36 → '36 小时'。r   r   u    天gu    小时)intr   r   r   r   format_duration$   s   r$   )   限时   报名截止u   截止tokenskindc                 C   s8  d}d}d}t | }tdd | D sT|dkr,| r,t| d }|dur+|}| dd }n(|dkrTdD ]!}t| |krStd	| | d }|rS|}| d|  } nq2td
d t|D d}	|	du rr|||d	| poddS d	|d|	  p~d}
|	}t	 }|t|k r|| }||v rd| dS |
| |dkr|d t|krdS t||d  }|du rdS |}|d7 }q|dkrdnd}d}d}dD ]'}|d | t|krtd	||d |d |  }|r|}d| } nq|du r| dS |dkr	|}n|}||7 }|t|k s||||
dS )u,  解析创建指令参数：返回 {'hours','deadline','signup_deadline','description'} 或错误消息。

    新语法（关键字）：<描述> 限时 <时限> 报名截止 <时间> 截止 <时间>
    旧语法兼容：无关键字时，接龙尾部识别时限、匹配尾部识别截止。
    g      H@Nc                 s   s    | ]}|t v V  qd S N_CREATE_KEYS).0tr   r   r   	<genexpr>8   s    z'_parse_create_params.<locals>.<genexpr>relaymatch)r   r    c                 s   s     | ]\}}|t v r|V  qd S r)   r*   )r,   ir-   r   r   r   r.   F       )r   deadlinesignup_deadlinedescriptionu	   参数「u	   」重复r%   r   u-   限时缺少参数，示例：限时 48小时u-   限时格式错误，示例：48小时 / 2天r   r&   r6   r5   r   u/    时间格式错误，示例：2026-09-15 20:00)listanyr    lenr   joinnext	enumeratestripsetadd)r'   r(   r   r5   r6   desc_tokenshwindowr   	first_keyr7   r3   seenr-   keyconsumedcandr   r   r   _parse_create_params.   s   
"


 rI   c                   C   s   t  dS )Nr   )r   nowr   r   r   r   r   _nowv   s   rK   user_idc                 G   s   |  dt|t|d d S )Nsend_private_msg)rL   message)call_apir"   r8   )apirL   rN   r   r   r   _send_private|   s   rQ   group_id	text_bodyc                 C   s    |  dt|t|gd d S )Nsend_group_msg)rR   rN   )rO   r"   r   )rP   rR   rS   r   r   r   _announce_group   s    rU   c                	   @   sz  e Zd ZdZdZdefddZdefddZdefd	d
Z	dedefddZ
d:defddZdd Zdee fddZdeeee f fddZdededee dee fddZdedee d efd!d"Zd edee fd#d$Zdee fd%d&Zd'd( Zdee fd)d*Zd+ed,efd-d.Zd+ed,efd/d0Zded efd1d2Zd+ed,efd3d4Zd+efd5d6Zd+ed,efd7d8Zd9S );ActivityPluginactivityu!   群活动：接龙与匹配下家r   c                 C   s<   | j jD ]}|ddkr|di dd   S qdS )Ntyper   data )	bot_eventrN   getr>   )selfsegr   r   r   _first_text   s
   zActivityPlugin._first_textc                 C   sF   | j j}|rt|tr|dp|dpd| j j S d| j j S )Ncardnicknameu   用户)r[   sender
isinstancedictr\   rL   )r]   rb   r   r   r   _sender_nickname   s   "zActivityPlugin._sender_nicknamerL   c                 G   s   t | j|g|R   d S r)   )rQ   rP   )r]   rL   rN   r   r   r   rQ      s   zActivityPlugin._send_privaterR   rS   c                 C      t | j|| d S r)   rU   rP   r]   rR   rS   r   r   r   rU         zActivityPlugin._announce_grouprN   c                 C   s\   |dkrdS |   }|sdS | }|d dkr | jjd ur dS |d dkr,| jjr,dS dS )NrN   Fr      /活动T   /提交)r_   splitr[   rR   
is_private)r]   
event_typebodypartsr   r   r   r1      s   zActivityPlugin.matchc                 C   sh   z$|    }|d dkr| |dd   W d S | |dd   W d S  ty3   td Y d S w )Nr   rj   r   u   Activity 处理异常)r_   rl   _route_group_command_handle_submit	Exceptionr   	exception)r]   rp   r   r   r   handle   s   zActivityPlugin.handleargsc                 C   s  t | jj}| jjd u rd S d }|r1|d  r1| jj|t|d }|s0| j	t
d d S n0| jj|}t|dkrC|d }nt|dkraddd |D }| j	t
d| d d S |sm| j	t
d	 d S | jj|d
 |}|r|d dkr| j	t
d d S |d dkrt| jj|d
 }|r|d |kr| j	t
d d S |  \}}	|s|	s| j	t
d d S | |d
 |d |	}
t|
t|	kr| j	t
d d S t }| jjj|d
 |d|pd |
rt|
nd |d | j	t
d | jj|d
 }|d dkr<| |d d|d  d|d  d t| j| j|||d s:t| j| j| d S d S | |d |d  d | ||| tdd |D rdt| j| j| d S d S )Nr   u   活动编号无效r   u   、c                 s   s    | ]	}t |d  V  qdS )idN)str)r,   ar   r   r   r.          z0ActivityPlugin._handle_submit.<locals>.<genexpr>u:   你参与了多个活动，请使用 /提交 <活动id>（   ）u$   你不在任何进行中的活动中rw   statuspendingu'   你已提交过作品或不在活动中rX   r/   rL   u   还没轮到你提交u0   请随 /提交 附上作品（文字或图片）sequ$   作品图片下载失败，请重试done)r|   contentimagessubmitted_atu   提交成功！rR   u   第 u    棒 ra   u    完成接力u    提交了作品c                 s       | ]	}|d  dv V  qdS r|   )r   leftNr   r,   r   r   r   r   r.      rz   )rx   r[   rL   isdigit	dbmanagerrW   $get_running_activity_for_user_and_idr"   rP   send_msgr   get_running_activities_for_userr:   r;   
get_memberr   get_members_extract_submission_download_imagesrK   update_memberjsondumpsrU   _relay_advance_finish_activity_forward_workall)r]   rv   uidactactsidsmembercurr   image_filessavedrJ   membersr   r   r   rr      st   

zActivityPlugin._handle_submitc                 C   s   g g }}| j jD ]-}|ddkr ||di dd q	|ddkr6||di dd  q	d| }|drM|tdd	  }|dd
}t|dkre|d 	 re|d
  }|dd |D fS )uT   从消息段提取正文与图片文件（命令文本之后的部分为正文）。rX   imagerY   filerZ   r   r2   rk   Nr   r   r   c                 S   s   g | ]}|r|qS r   r   )r,   fr   r   r   
<listcomp>       z6ActivityPlugin._extract_submission.<locals>.<listcomp>)
r[   rN   r\   appendr>   r;   
startswithr:   rl   r   )r]   
text_partsr   r^   ro   spr   r   r   r      s   

z"ActivityPlugin._extract_submissionactivity_idr~   r   c                 C   s   ddl m} g }t|dD ]8\}}tj|d pd}t||||}	| j	|}
|
s/g   S ||
|	\}}|s<g   S |
tj|	 q|S )Nr   )download_imager   z.jpg)
core.utilsr   r=   ospathsplitextarchive_mod
image_pathrP   get_image_urlr   basename)r]   r   r~   r   r   r   nr   extlocalurlok_r   r   r   r      s   zActivityPlugin._download_imagesr   r   r   c                    s   | j j|d  d }|r|d dkrdS |d  d kr dS t fdd|D  }| jt d td	|d
  dg| |R   dS )uR   match：把 member 的作品转发给其下家（收件人已完成也照送）。rw   next_user_idr|   r   NrL   c                 3   s$    | ]}|d   d  kr|V  qdS rL   Nr   r   r   r   r   r.     s   " z/ActivityPlugin._forward_work.<locals>.<genexpr>u$   你收到了一份作品（活动「title	   」）：)r   rW   r   r<   rQ   r"   r   _work_segments)r]   r   r   r   	recipientfreshr   r   r   r     s   
zActivityPlugin._forward_workc                 C   s   t |S r)   )r   )r]   r   r   r   r   r     s   zActivityPlugin._work_segmentsc                 C   s   | j j}t| j j}|s|   d S |d }|dkr%| |dd   d S |dkr1| || d S |dkr=| || d S |dkrI| || d S |dkrT| 	| d S |dkr`| 
|| d S |   d S )	Nr   u   创建r   u   加入   退出u   开始u   状态u   结束)r[   rR   rx   rL   _show_usage_handle_create_handle_join_handle_leave_handle_start_handle_status_handle_end)r]   rv   gidr   subr   r   r   rq     s&   z#ActivityPlugin._route_group_commandc                 C   s   | j td d S )Nu   活动指令：
/活动 创建 接龙 <标题> [每人时限]（如 48小时 / 2天）
/活动 创建 匹配 <标题> <截止 YYYY-MM-DD HH:MM>
/活动 加入 / 退出
/活动 开始（创建人）
/活动 状态 / 结束（创建人）)rP   r   r   r]   r   r   r   r   4  s   
zActivityPlugin._show_usagec              
   C   s  | j j}t| j j}|r|d dvr| jtd d S | jj	|r-| jtd d S |d }|dd  }|sEd}| jt| d S |d }t
|dd  |dkrUdnd	}t|trg| jt| d S |dkr| jjj|d||d
 ||d |d |d d}	d| d|	 ddt|d  g}
n2|d s| jtd d S | jjj|d	||d
 ||d |d d}	d| d|	 dd|d  g}
|d
 r|
d|d
   |d r|
d|d  d |
d | jtd|
 d S )Nr   )   接龙u   匹配u@   用法：/活动 创建 接龙|匹配 <标题> [描述] [参数]u   本群已有进行中的活动r   u   用法：/活动 创建 接龙 <标题> [描述] [参数]
参数：限时 <时限> / 报名截止 <时间> / 截止 <时间>，如：
/活动 创建 接龙 端午 大家自由创作 报名截止 2026-08-10 20:00 截止 2026-08-20 20:00 限时 2天r   r/   r1   r7   r   r5   r6   )hours_per_userr5   r6      接龙活动「u   」已创建（#r{   u   每人限时 uG   匹配活动必须设定截止时间，示例：截止 2026-09-15 20:00)r5   r6      匹配活动「u   截止时间 	   描述：u   报名截止：u   （到点自动开始）uI   回复 /活动 加入 报名，报名完成后由创建人 /活动 开始
)r[   rR   rx   rL   rP   r   r   r   rW   get_active_activityrI   rc   create_activityr$   r   r;   )r]   rv   r   r   r(   restusager   paramsaidlinesr   r   r   r   >  sX   

zActivityPlugin._handle_creater   r   c                 C   s   | j j|}|r|d dkr| jtd d S | j j|d |r-| jtd d S | j j|d ||   | j j	|d }| jtd|d  d| d	 d S )
Nr|   openu$   本群当前没有报名中的活动rw   u   你已加入该活动u   已加入「r   u   」（当前 u    人）)
r   rW   r   rP   r   r   r   
add_memberre   count_members)r]   r   r   r   r   r   r   r   r   o  s   &zActivityPlugin._handle_joinc                 C   s4  | j j|}|s| jtd d S | j j|d |}|s)| jtd d S |d dkr| j j|d | | j j|d dkrY| j jj	|d dd | jtd	 d S t
|d
 |kr| j j|d d }| j jj	|d |d d | jtd|d   d S | jtd d S | || d S )N   本群没有进行中的活动rw   u   你不在该活动中r|   r   r   	cancelledr|   u'   已退出，活动无人参加已取消
created_byrL   )r   u"   已退出，创建人已转移给 ra   u   已退出报名)r   rW   r   rP   r   r   r   remove_memberr   update_activityrx   r   _handle_leave_running)r]   r   r   r   r   firstr   r   r   r   {  s*   zActivityPlugin._handle_leavec                 C   s  | j j|d }t|}| j jj|d |d dd | j j|d }| |d |d  d |d d	kr]|rW|d |d krYt| j| j |||d
 s[t| j| j | dS dS dS dS t	| j| j ||d | | j j|d }t
dd |D rt| j| j | dS dS )uR   进行中退出：接龙摘链（仅当轮到 TA 时顺延），匹配闭合环。rw   rL   r   r   rR   ra   u    已退出活动rX   r/   r~   c                 s   r   r   r   r   r   r   r   r.     rz   z7ActivityPlugin._handle_leave_running.<locals>.<genexpr>N)r   rW   r   r   r   rU   r   rP   r   _match_reconnectr   )r]   r   r   r   r   r   r   r   r   r     s    z$ActivityPlugin._handle_leave_runningc                 C   s   | j j|}|s| jtd d S |d dkr#| jtd d S t|d |kr9|  s9| jtd d S t| j| j |}|rM| jt| d S d S )N   本群没有活动r|   r   u   活动已开始r   u!   只有创建人才能开始活动)	r   rW   r   rP   r   r   rx   
super_user_start_activity)r]   r   r   r   errr   r   r   r     s   zActivityPlugin._handle_startc           	      C   sj  | j j|}|s| jtd d S | j j|d }d|d  d|d dkr*dnd	 d
|d  dg}|drD|d|d   dddddd}|D ]}|d|d  d|d  d||d |d   qN|d dkrt	|}|r|d|d   n&|d n t
dd |D }|d| d t|  |d!|d"   | jtd#| d S )$Nr   rw   u   「r   u   」（rX   r1   u   匹配下家r   z #r{   r7   r   u   ✓u   跳过u   未交r   u   …)r   skippedmissedr   r}   z  r~   z. ra   r2   r|   r/   u   当前轮到：u   接龙已完成c                 s   s     | ]}|d  dkrdV  qdS )r|   r   r   Nr   r   r   r   r   r.     r4   z0ActivityPlugin._handle_status.<locals>.<genexpr>u	   进度：/u	   截止：r5   r   )r   rW   r   rP   r   r   r   r\   r   r   sumr:   r;   )	r]   r   r   r   r   
status_mapr   r   r   r   r   r   r     s(   2
6zActivityPlugin._handle_statusc                 C   s   | j j|}|s| jtd d S t|d |kr)|  s)| jtd d S |d dkrJ| j jj|d dd | jtd	|d
  d d S t	| j| j | d S )Nr   r   u!   只有创建人才能结束活动r|   r   rw   r   r   	   活动「r   u   」已取消)
r   rW   r   rP   r   r   rx   r   r   r   )r]   r   r   r   r   r   r   r     s    zActivityPlugin._handle_endN)rN   )__name__
__module____qualname__namer7   rx   r_   re   r"   rQ   rU   boolr1   ru   r8   rr   tupler   r   rd   r   r   rq   r   r   r   r   r   r   r   r   r   r   r   r   rV      s.    8"
1rV   r   c              	   C   s~   g }|  dr|t| d  z|  drt| d ng }W n ttfy-   g }Y nw |D ]}|dd|id q0|S )Nr   r   r   r   )rX   rY   )r\   r   r   r   loads	TypeErrorr   )r   segsnamesr   r   r   r   r     s   
 r   r   r   from_seqc           	   	   C   s   ddl m}m} |||}|sdS |||d }|r;|d s#|dr;t| t|d td|d	  d
gt|R   |jj	|d |d t
 d t| |d d|d  dt|d  d dS )u_   把作品顺延给 from_seq 之后第一个 pending 成员。返回 False 表示链已走完。r   )next_pending	last_doneFr~   r   r   rL   u   接力作品（活动「r   r   rw   received_atrR   u   轮到 ra   u    接力！请于 r   u%    内完成，私聊 /提交 作品。T)logicr   r   r\   rQ   r"   r   r   rW   r   rK   rU   r$   )	rP   dbr   r   r   r   r   targetprevr   r   r   r     s$   
r   left_uidc                    s   t  fdd|D d}|sdS t  fdd|D d}|r"|d nd}|jj|d |d |d |d	 d
krEt| t|d td dS dS )ua   匹配环闭合：left_uid 的前驱 next 改为其后继（Y→X→D 退出 X 后变 Y→D）。c                 3        | ]}|d   kr|V  qdS )r   Nr   r   r  r   r   r.      r4   z#_match_reconnect.<locals>.<genexpr>Nc                 3   r  r   r   r   r  r   r   r.     r4   r   rw   rL   )r   r|   r}   uE   你的下家已退出，请继续创作，活动截止时间不变。)r<   rW   r   rQ   r"   r   )rP   r   r   r  r   predr   new_nextr   r  r   r     s   r   member_countc                 C   s   z
t |d d}W n ttfy   Y dS w |dpd}t  t|d|  }||krKt| |d d|d  d	|d d
| dt	| d	 dS dS )ub   接龙全局截止早于最晚理论完成时间（开始 + 人数×限时）时群公告警告。r5   r   Nr   r   r#   rR   u   ⚠️ 提醒：截止 u     早于最晚理论完成时间 u   （u    人 × 每人 u6   ），到点未完成的接力将直接截断归档。)
r   r   r   r   r\   rJ   r   rU   r   r$   )rP   r   r  duer   	worst_endr   r   r   _warn_relay_deadline_conflict  s&   r
  c                 C   sh   t  }|jj|d d|d |j|d }|j|d }t|| t| |d d|d  d d S )Nrw   finished)r|   finished_atrR   r   r   u   」结束，已归档！)rK   rW   r   get_activityr   r   archive_activityrU   )rP   r   r   rJ   r   r   r   r   r   r     s    r   c                 C   s  |j |d }dd |D }dd |D }|d dkr%|s dS t|}nt|d	k r-d
S t|}dd t|D }|j |d | |j j|d dd t }|d dkr|	drct
| |t| |d }	|j j|d |	d |d t| t|	d td|d  dt|d  d t| |d d|d  d||	d   d dS |D ]\}
}}t| t|
td|d  d||  d|d  d qt| |d d|d  d dS )u   开始活动：校验人数、生成链/环、置 running、私聊通知。返回错误消息或 None。

    供手动 /活动 开始 与心跳报名截止自动开始共用。
    rw   c                 S   s   g | ]}|d  qS )rL   r   r   r   r   r   r   -  r   z#_start_activity.<locals>.<listcomp>c                 S   s   i | ]	}|d  |d qS )rL   ra   r   r   r   r   r   
<dictcomp>.  s    z#_start_activity.<locals>.<dictcomp>rX   r/   u   接龙活动至少需要 1 人r   u   匹配活动至少需要 2 人c                 S   s"   g | ]\}\}}|||d  fqS )r   r   )r,   r3   ur   r   r   r   r   7  s   " runningr   r5   r   r   r   r   uV   」开始！你是第 1 棒。
请创作并私聊发送 /提交 附上作品，限时 r   u   。rR   u   」开始，u
    先来！r   u   」开始！
你的下家是：u?   
请为 TA 创作并私聊发送 /提交 附上作品，截止 u   」开始，请查看私聊！N)rW   r   r
   r:   r	   r=   set_ringr   rK   r\   r
  r   rQ   r"   r   r$   rU   )rP   r   r   r   usersnick_mapassignsringrJ   r   uid_next_uid_seqr   r   r   r   '  sP   



r   c                   @   sJ   e Zd ZdZdZi ZddefddZdd Zd	e	d
e
fddZdd ZdS )ActivityTimerPluginactivity_timeru6   活动计时：接龙超时跳过、匹配截止结束metar   c                 C   sT   |dkrdS t  }| jt| jd}| | dk rdS | | jt| j< dS )Nr  Fr   <   T)r   rJ   
_last_scanr\   rX   r   	timestamp)r]   rn   rJ   lastr   r   r   r1   Z  s   zActivityTimerPlugin.matchc                 C   s.   z|    W d S  ty   td Y d S w )Nu   ActivityTimer 处理异常)_scanrs   r   rt   r   r   r   r   ru   d  s
   zActivityTimerPlugin.handlerR   rS   c                 C   rf   r)   rg   rh   r   r   r   rU   j  ri   z#ActivityTimerPlugin._announce_groupc              
   C   s   ddl m}m} t }| jj D ]I}|d dks|ds qz
t	|d d}W n t
tfy5   Y qw ||kr[t| j| j|}|r[| jjj|d dd	 | |d
 d| d q| jj D ]}| jj|d }|d dkr||}|r||d||dpdr| jjj|d |d dd	 | |d
 |d  d | jj|d }t| j| j|||d st| j| j| |d}	zt	|	d}W n t
tfy   Y qbw ||kr|D ]}
|
d dkr| jjj|d |
d dd	 qt| j| j| qbd S )Nr   )
is_timeoutr   r|   r   r6   r   rw   r   r   rR   u   报名截止，u   ，活动已取消rX   r/   r   r   r   rL   r   ra   u    超时未完成，跳过r~   r5   r}   r   )r   r"  r   r   rJ   r   rW   get_active_activitiesr\   r   r   r   r   rP   r   rU   get_running_activitiesr   r   r   r   )r]   r"  r   rJ   r   r  r   r   r   r5   r   r   r   r   r!  m  sX    
zActivityTimerPlugin._scanN)r  )r   r   r   r   r7   r  r   r1   ru   r"   rx   rU   r!  r   r   r   r   r  S  s    
r  )*r   r   r   r   r   	core.baser   core.cqr   core.loggerr   r   r   r   r	   r
   r   rZ   r   r   rx   r   r   r    r$   r+   r8   rd   rI   rK   r"   rQ   rU   rV   r   r   r   r   r
  r   r   r  r   r   r   r   <module>   s<    
H  Y	,