
    iJ                    x   d Z ddlm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ZddlmZ ddlmZ dd	lmZ dd
lmZmZ ddlmZ ddlmZ  ej2                  d      Zd"dZd#dZd$dZd%dZdddddddZd&dZ 	 	 	 	 	 	 	 	 d'dZ!	 	 	 	 	 	 	 	 	 	 d(dZ"d&dZ#d&dZ$d&dZ%d&dZ&	 	 	 	 	 	 	 	 	 	 d)d Z'd*d+d!Z(y),u  
LINE 事件 ↔ Cooper 內部頻道/訊息系統的橋接層(階段 6)。

職責:
  1. 每個 LINE source(group / room / 1對1)對應一個 Cooper Channel
     - 第一次看到時自動建一個 channel_type='line_<type>' 的 Channel
  2. 每個 LINE userId 對應一個 Cooper User
     - 沒綁定的會建立「影子帳號」(password_hash=""),日後可由 owner 合併到實體帳號
  3. message 事件 → 存成 Channel 的 Message → 觸發 tagging / index / Cooper 回應
  4. follow / join / leave 事件 → 維護 Channel 的存在 / 軟刪
  5. Cooper 回應一律用 push_text(LLM 跑完通常超過 reply token 的 1 分鐘 TTL)

設計原則:整段都是 best-effort,單一事件失敗只 log,不要把整批 events 拖垮。
    )annotationsN)datetimetimezone)Any)UUID)select)settings)AsyncSessionLocal)ChannelMessage)User)line_clientzcoba.line.bridgec                    | j                  d      }|dk(  rd| j                  d      fS |dk(  rd| j                  d      fS |dk(  rd| j                  d      fS y)	u+   source dict → (source_type, source_id)。typegroupgroupIdroomroomIduseruserIdNN)get)srcstypes     </volume1/homes/robertsu/coba/app/app/services/line_bridge.py_resolve_sourcer   *   sd    GGFOE	***swwx(((swwx(((    c           
       K   t        |      \  }}|r|sy | j                  t        t              j	                  t        j
                  |k(  t        j                  j                  d                    d {   j                         }||S dddd|   }t        |dd| dd| ||d	d
      }| j                  |       | j                          d {    |S 7 d7 w)Nu   LINE 群組u   LINE 多人聊天室u   LINE 私訊)r   r   r   u   📱u	   從 LINE u    自動建立line_F2   )nameicondescriptionchannel_typeline_source_typeline_source_id
is_default
sort_order)r   executer   r   wherer&   
deleted_atis_scalar_one_or_noneaddflush)dbr   r   sidchr!   s         r   _get_or_create_channelr3   6   s      %JE3

w""c)""4(	
 
   
~	 & 	D
 
wm4UG_	
B FF2J
((*I52 s%   A3C5C6ACCCCc           	     |  K   |sy | j                  t        t              j                  t        j                  |k(  t        j
                  j                  d                    d {   j                         }||S |j                  d      dk(  r|j                  d      nd }t        j                  ||       d {   }|xs i j                  d      xs d|d d  }|xs i j                  d      }t        d	| d
d||d|d      }| j                  |       	 | j                          d {    |S 7 7 |7 
# t        $ rr | j                          d {  7   | j                  t        t              j                  t        j                  |k(               d {  7  j                         }Y |S w xY ww)Nr   r   r   )group_iddisplayNamezLINE-   
pictureUrlr   z@cooper.local memberT)emailpassword_hashdisplay_name
avatar_urlroleline_user_id	is_active)r)   r   r   r*   r@   r+   r,   r-   r   r   get_profiler.   r/   	Exceptionrollback)r0   r@   r   ur5   profiler=   avatars           r   _get_or_create_userrH   X   s    zzt4,,<doo>Q>QRV>WX 
  	} &)WWV_%?swwy!TH++L8LLGMr&&}5S5bqAQ@R9SLm  .F 	l^=1!!	A FF1I hhj H?
 M  	  kkm::4Lt00L@A
 
 
 	
 H su   A#F<%D8&AF<=D:>AF<D> 1D<2D> 6F<:F<<D> >F9EAF9!F$"F95F<8F99F<c                  K   | j                  d      }| j                  d      xs i }	 |dk(  rt        | |       d{    y|dk(  rt        | |       d{    y|dk(  rt        | |       d{    y|dk(  rt	        | |       d{    y|dk(  rt        | |       d{    yy7 n7 V7 >7 &7 # t        $ r t        j                  d	|       Y yw xY ww)
u(   LINE webhook 拋進來的單一事件。r   sourcemessageNfollowunfollowjoinleaveu   LINE 事件處理失敗 type=%s)	r   _handle_message_handle_follow_handle_unfollow_handle_join_handle_leaverC   logger	exception)eventetyper   s      r   handle_eventrY      s     IIfE
))H

#CCI!%---h ,,,j "5#...f_uc***gs+++  .,.*+ C:EBCs   'CB7 B-B7 CB7 B/B7 CB7 2B13B7 7C8B7 B3B7 CB7 &B5'B7 +C-B7 /B7 1B7 3B7 5B7 7CCCCu   [圖片]u   [語音]u   [影片]u   [檔案]u   [貼圖]u   [位置])imageaudiovideofilestickerlocationc           	       K   | j                  d      xs i }|j                  dd      }|j                  d      }|j                  d      }| j                  d      }t               4 d {   }t        ||       d {   }|	 d d d       d {    y t        |||       d {   }	|dk(  r|j                  dd      }
d}nR|t        v rBt        |   }d}|dk(  rd	|j                  d
d       }| | j                         }
|dv r|nd}nd| d}
d}t        |j                  |	r|	j                  nd |
||      }|j                  |       |j                          d {    |j                  }|j                  }|j                  }d d d       d {    	 ddlm} ddlm}  |        ||	r|	j                  nd 
t        j                   t"        j$                        j'                         d        dk(  r	r|	j                  r	 ddlm}  |       |dk(  r	 ddlm}  |       |dv r2|r0t7        j8                  t;        ||	r|	j                  nd              |dk(  r4|r2ddlm}  |
      }|r"t7        j8                  tA        |||             y |dk(  r?ddl!m"}m#}  |
      r/t7        j8                  tI        	r|	j                  nd |             y |dk7  ry |j                  d      dk(  }ddl%m&} |rd}nEt               4 d {   } |
	r|	j                  nd |       d {   }d d d       d {    s!dd l!m'} t7        j8                   |             |sy t7        j8                  tQ        |             y 7 .7 7 7 7 @7 # 1 d {  7  sw Y    xY w# t(        $ r t*        j-                  d       Y w xY w# t(        $ r t*        j-                  d       Y w xY w# t(        $ r t*        j-                  d       Y w xY w7 7 7 # 1 d {  7  sw Y   xY ww)!NrK   r   textidr   
replyTokenr9   r]    fileName)rZ   r[   r]   u   [未支援訊息:])
channel_iduser_idcontentmessage_typeline_message_idr   )trigger_tagging)trigger_index)
message_idrg   rh   ri   created_at_isotagsu   LINE message 背景索引失敗)trigger_acceptanceu   trigger_acceptance 失敗)trigger_acku   trigger_ack 失敗rZ   r[   r\   r]   )rg   	sender_id)parse_bind_command)is_cancel_commandcancel_last_auto_taskr   )detect_coba_mentionT)rg   rh   r0   )trigger_task_extract))r   r
   r3   rH   _TEXT_PREFIX_MAPstripr   rb   r.   commitr&   app.services.coba_taggerrl   app.services.coba_memoryrm   r   nowr   utc	isoformatrC   rU   rV   app.services.task_acceptancerq   app.services.coba_acknowledgerr   asynciocreate_task_download_mediaapp.services.line_bindingru   _handle_bind_commandapp.services.task_extractorrv   rw   _handle_cancel_commandapp.services.coba_chatrx   ry   _cooper_respond_to_line)rW   r   msg_objmtyperk   r@   reply_tokenr0   r2   senderri   rj   prefixextramsgmsg_idch_idch_line_source_idrl   rm   rq   rr   ru   	bind_coderv   rw   is_dmrx   should_respond
_detect_dbry   s                                  r   rP   rP      s    ii	"(bGKK'Ekk$'O778$L))L)K ""b)"c22: #"" +2|SAAF?kk&"-G!L&&%e,FEGKK
B789(..0G$)-G$G5VL)%2G!Luu!'FIIT%+
 	siik--? #"D<<:!'FIIT#<<5??A	
 v&VYY	:Gv& v	3A
 33OU#)fiit
 	 v,@&w/	 4<):K!   vXW% 6#		/@+!   vGGFOv%E: %&&*#6 %+			$ N '& D089/v?PR]^_o #2 # B0 	9 #"""\  <:;<  	:89	:  	312	3N ' '&&&s`  A)Q+N,Q/N+?N N+QNQN+(N")B<N+%N%&(N+QN(QAO >QO&  Q&P 4C&QP0QP7<P3=P7QP5AQN+Q"N+%N+(Q+N>1N42N>9QO#Q"O##Q&PQPQP-)Q,P--Q3P75Q7Q	=Q >Q	Qc                   K   ddl m} | y || |       d{   }|rDd}|rt        j                  ||       d{   }|st        j                  ||       d{    yyy7 K7 )7 w)u5   處理「@庫柏 取消最後一個任務」指令。r   )rw   NF)r   rw   r   
reply_text	push_text)rh   r&   r   rw   ra   sents         r   r   r   #  sw      B&w??D$//TBBD''=== 	  @ C=s1   A*A$#A*A&A*A(A*&A*(A*c                  K   ddl m}m} ddlm} t               4 d{   }	  ||| |       d{   }|j                          d{    d|j                   d|j                   d}	ddd      d{    |rDd}|rt        j                   |	       d{   }|st        j"                  |	       d{    yyy7 7 7 |# |$ r}
d|
 }	Y d}
~
ld}
~
w|$ r |j                          d{  7   d	}	Y t        $ rP}
|j                          d{  7   t        j                  d
       dt        |
      j                   }	Y d}
~
d}
~
ww xY w7 # 1 d{  7  sw Y   xY w7 7 w)u   跑 LINE 綁定 + 回 LINE。r   )BindingErrorredeem_code)IntegrityErrorNuc   ✓ 綁定成功!
你的 LINE 現在連到「山水話菁英團隊」行程規畫系統的帳號「(u8   )」。
之後我用 LINE 找你都會用這個身份。u   ⚠ u>   ⚠ 綁定遇到資料衝突,請稍後再試或聯絡管理員u   綁定 redeem 失敗u   ⚠ 綁定失敗:F)r   r   r   sqlalchemy.excr   r
   r|   r=   r?   rD   rC   rU   rV   r   __name__r   r   r   )coder@   r&   r   r   r   r   r0   web_userra   er   s               r   r   r   5  sn     D- ""b	:(T<@@H))+ddldydyczz{  }E  }J  }J  |K KCD 	 #"& $//TBBD''=== 	 ' #@  	!:D 	T++-SD 	:++-34&tAw'7'7&89D	: #""", C=s   E3CE3EC	CC	
C!C	,E37E8#E3E/E3;E1<E3C	C		ECEE3C64E;E=EED2EEEEE3E, E#!E,(E31E3c                   K   |j                  d      }| j                  d      }|syd}|rt        j                  ||       d{    yt        j                  ||       d{    y7 $7 w)u4   使用者把 Cooper 加為好友 → 發歡迎詞。r   rc   Nu  嗨,我是庫柏(Cooper)——「山水話菁英團隊」的 AI 同事。

我駐紮在團隊的「行程規畫系統」裡。
把我拉進團隊的 LINE 群組,我會默默讀訊息、在 @ 我的時候回應,
也會處理會議錄音、每天 9 點推送個人簡報。

現在你可以:
• 直接傳訊息給我(我會用 Sonnet 回)
• 邀我進群組
• 到網頁版產生綁定碼,把 LINE 帳號連到行程規畫系統)r   r   r   r   )rW   r   r@   r   welcomes        r   rQ   rQ   \  sm     778$L))L)K	R  $$[':::##L'::: 	;:s$   AA.A*A.$A,%A.,A.c                V   K   t         j                  d|j                  d             yw)u9   使用者封鎖 / 移除 Cooper。先不做事,只 log。zLINE unfollow: userId=%sr   N)rU   infor   )rW   r   s     r   rR   rR   r  s     
KK*CGGH,=>s   ')c                  K   | j                  d      }t               4 d{   }t        ||       d{   }|j                          d{    |r|j                  nd}ddd      d{    d}|rt        j                  ||       d{    yrt        j                  ||       d{    yy7 7 7 m7 O# 1 d{  7  sw Y   _xY w7 D7 %w)uM   Cooper 被拉進群 / 多人聊天室 → 預先建 Channel + 自我介紹。rc   Nu   嗨大家好,我是庫柏(Cooper),「山水話菁英團隊」的 AI 同事。
在這個群裡 @ 我或開頭叫「庫柏」我就會回應。
我會默默讀訊息建立記憶,需要時再叫我。)r   r
   r3   r|   r&   r   r   r   )rW   r   r   r0   r2   r   intros          r   rS   rS   w  s     ))L)K ""b)"c22iik13B-- #"	B 

 $$[%888	##$5u=== 
 #2 #""" 	9=s    CB8CC B:C B<C #C.B>/!CC C1C2C:C <C >C CC	CCCc           	     j  K   t        |      \  }}|syt               4 d{   }|j                  t        t              j                  t        j                  |k(  t        j                  j                  d                   d{   j                         }|	 ddd      d{    yt        j                  t        j                        |_        |j                          d{    ddd      d{    t        j!                  d|       y7 7 7 p7 17 ## 1 d{  7  sw Y   3xY ww)uF   Cooper 被踢出群 → 軟刪 Channel(訊息保留供日後 audit)。Nu'   LINE leave: source=%s 已軟刪 channel)r   r
   r)   r   r   r*   r&   r+   r,   r-   r   r   r   r   r|   rU   r   )rW   r   _r1   r0   r2   s         r   rT   rT     s     S!FAs ""bJJ7O!!&&#-""&&t,
 

  	 : #"" !X\\2iik #" KK93? #
 # 	 #"""s    D3DD3A DDDD3'D(D3-;D(D)D-D38D9D3DD3DD3D0$D'%D0,D3c           
       K   t         j                  s&t         j                  st        j	                  d       y|syddlm} 	  || |       d{    t               4 d{   }|j                  t        t              j                  t        j                  j                  d      t        j                   | k(  t        j"                  j                  d            j%                  t        j&                  j)                               j+                  d             d{   j-                         j/                         }|r|j0                  nd}ddd      d{    d}|rt3        j4                  |       d{   }|st3        j6                  |       d{    yy7 T# t        $ r t        j                  d       Y sw xY w7 i7 7 u# 1 d{  7  sw Y   xY w7 j7 Lw)	u   LINE 路徑的 Cooper 回應 → 全程走 coba_chat.coba_respond_to_mention(含 tool use 工具)
    跑完後把產出的 Cooper 訊息推回 LINE 群。
    uP   ANTHROPIC_API_KEY 跟 CLAUDE_CODE_OAUTH_TOKEN 都沒設,LINE Cooper 回應跳過Nr   )coba_respond_to_mentionu   Cooper 回應(LINE)失敗   u   (庫柏沒回應)F)r	   ANTHROPIC_API_KEYCLAUDE_CODE_OAUTH_TOKENrU   warningr   r   rC   rV   r
   r)   r   r   r*   rh   r,   rg   r+   order_by
created_atdesclimitscalarsfirstri   r   r   r   )	rg   triggered_msg_idr&   r   r   r0   lastra   r   s	            r   r   r     s~     &&(*J*Jij ?6%j2BCCC !""bjj7OU7??&&t,g.@.@J.NPWPbPbPfPfgkPlmXg((--/0U1X	
 

 79UUW 	  $t||)< #" D ++K>>##ND999 ' 	D 6456
 #
 #""" ?9s   A G+F& F#F& G+"G#G+&B8GG0GG+G!G+<G'=G+G)G+#F& &GG+GG+GG+G$GG$ G+)G+c           	       K   ddl }ddlm} t        j                  |        d{   }|sy |t
        j                        }|j                  dd       dddd	d
j                  |d	      }||  | z  }		 |	j                  |       t        j                  d| |	t        |             |dv r^|rZ|rV	 ddlm}
 ddlm} ddlm} t)               4 d{   }d |
j*                         j-                  d       } |||t/        |	      |	j0                  |d      }|j3                  |       |j5                          d{    |j7                  |       d{    |j8                  }|j;                  t=        t>              jA                  t>        j8                  |k(               d{   jC                         }|r|jD                  nd}ddd      d{     |       rt        jF                  |d       d{    yyyyy7 # t        $ r t        j                  d|	       Y yw xY w7 `7 7 7 7 b# 1 d{  7  sw Y   rxY w7 Q# t        $ r t        j                  d       Y yw xY ww)u   下載 LINE 上傳的 image / audio / video / file 到本機 LINE_MEDIA_DIR。

    階段 8:audio/video → 自動建 Meeting + 觸發轉錄/摘要/action_item → Task pipeline。
    r   N)PathT)parentsexist_okz.jpgz.m4az.mp4z.binrs   u!   LINE 媒體下載 %s → %s (%dB)u   LINE 媒體寫檔失敗 %s)r[   r\   )r   )Meeting)trigger_meeting_processingu   LINE 錄音 z%m/%d %H:%Mpending)titlerg   
audio_pathaudio_filenameuploaded_bystatusu`   📥 收到錄音了,庫柏在處理中...
約 1-3 分鐘後我會把摘要 + 任務丟回來。u&   LINE audio → meeting pipeline 失敗)$ospathlibr   r   get_message_contentr	   LINE_MEDIA_DIRmkdirr   write_bytesrU   r   lenrC   rV   r   app.models.meetingr   app.services.meeting_processorr   r
   r   strftimestrr!   r.   r|   refreshrb   r)   r   r   r*   r-   r&   r   )rk   r   rg   rt   r   r   data	media_dirextoutr   r   r   r0   r   m
meeting_id
ch_line_ids                     r   r   r     sJ    
 00AADX,,-IOOD4O0Vff
M
Q
QRWY_
`C
)#/
/C7#sSWyY ""zi 	G)2Q(**b&|x||~'>'>}'M&NO)"3x#&88 )$ q	iik!!jjm##TT
$&JJ7O))'**
*BC% %%'  ;EZ66$
# +*( 'z2!++w   7 7@z" B  5s; + "# +***.  	GEF	Gs   #I;HAI;72H )I;5 I H5I A)I H8I H:AI -H<."I I H>'I II I;H2/I;1H22I;5I 8I :I <I >I  II	II I85I;7I88I;)r   dict[str, Any]returnztuple[str | None, str | None])r   r   r   zChannel | None)r@   
str | Noner   r   r   zUser | None)rW   r   r   None)rW   r   r   r   r   r   )rh   UUID | Noner&   r   r   r   r   r   )
r   r   r@   r   r&   r   r   r   r   r   )
rg   r   r   r   r&   r   r   r   r   r   r   )
rk   r   r   r   rg   r   rt   r   r   r   ))__doc__
__future__r   r   loggingr   r   typingr   uuidr   	anthropic
sqlalchemyr   
app.configr	   app.databaser
   app.models.channelr   r   app.models.userr   app.servicesr   	getLoggerrU   r   r3   rH   rY   rz   rP   r   r   rQ   rR   rS   rT   r   r    r   r   <module>r      s/   #   '      * /   $ 
		-	.	D"TC.  ~`B>>> > 
	>$!>
!>!> !> 	!>
 
!>N;,?
>&@0':':': ': 	':
 
':^8Gr   