Ë
    3Ú¯jÈ,  ã                  óà  — U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlmZmZmZ  ej$                  e«      j)                  «       j*                  j*                  Zd\  ZZdZdd„Zddd	œZd
ed<    e	j:                  «       Zdd„Zdd„Z dd„Z!dd„Z"dd„Z# G d„ d«      Z$	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd„Z%dd„Z&e'dk(  r ejP                   e&«       «       yy)uï  Twitch chat collector (IRC åŒ¿å��æŽ¥ç¶šã€� 2026-09-20)ã€‚

    python scripts/twitch.py <login> [--dry] [--duration SEC]   # å�˜ç™º ad-hoc (dry = DB ã�«æ›¸ã�‹ã�šè¡¨ç¤ºã� ã�‘)

daemon.py ã�‹ã‚‰ã�¯ `check_twitch_live()` (Helixã€� .env ã�« TWITCH_CLIENT_ID/SECRET ã�Œã�‚ã‚‹æ™‚ã� ã�‘) ã�¨
`collect_twitch()` ã‚’ä½¿ã�†ã€‚ Helix ã�Œç„¡ã�‘ã‚Œã�° LIVE æ¤œçŸ¥ã�ªã�—ã�§ IRC ã�«å¸¸æ™‚ JOIN ã�™ã‚‹ (åŒ¿å�� JOIN ã�¯èª�è¨¼ä¸�è¦�ã€�
ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ä¸­ã‚‚ãƒ�ãƒ£ãƒƒãƒˆã�¯æµ�ã‚Œã‚‹ã�®ã�§ã��ã�®ã�¾ã�¾é›†ã‚�ã‚‹)ã€‚

å�—ä¿¡ã�—ã�Ÿæœ¬æ–‡ã�¯ YouTube ã�¨å�Œã�˜çµŒè·¯ (mask â†’ normalize â†’ SqliteAppender) ã�«æµ�ã�™ã€‚ æŠ•ç¨¿è€…å�� / channel ã�¯ä¿�å­˜ã�—ã�ªã�„ã€‚
Twitch emote ã�¯ `emotes=` tag ã�®ä½�ç½®æƒ…å ±ã�§æœ¬æ–‡ã�‹ã‚‰é™¤ã�� (= YouTube ã�® sticker é™¤åŽ»ã�«ç›¸å½“)ã€‚
é    )ÚannotationsN)ÚSqliteAppenderÚmaskÚ	normalize)zirc.chat.twitch.tvi)  zFMozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/120.0c                 ó~  — i } t         dz  }|j                  «       r¡|j                  d¬«      j                  «       D ]~  }|j	                  «       }|r|j                  d«      sd|vrŒ+|j                  dd«      \  }}|j	                  «       j	                  d«      j	                  d«      | |j	                  «       <   Œ€ | S )	uR   `.env` (KEY=VALUEã€� # ã‚³ãƒ¡ãƒ³ãƒˆ) ã‚’èª­ã‚€ã€‚ python-dotenv ã�¯ä½¿ã‚�ã�ªã�„ã€‚z.envúutf-8)Úencodingú#ú=é   ú"ú')ÚROOTÚexistsÚ	read_textÚ
splitlinesÚstripÚ
startswithÚsplit)ÚenvÚpÚlineÚkÚvs        õL   /var/www/html/é…�ä¿¡ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–/videos/stream-comments/scripts/twitch.pyÚload_envr   !   s¢   € à€CÜˆv‰€AØ‡x�x„zØ—K‘K¨�KÓ1×<Ñ<Ó>ò 	=ˆDØ—:‘:“<ˆDÙ˜4Ÿ?™?¨3Ô/°3¸d±?ØØ—:‘:˜c 1Ó%‰DˆAˆqØŸW™W›YŸ_™_¨SÓ1×7Ñ7¸Ó<ˆC�—‘“	ŠNð	=ð €Jó    ç        )ÚvalueÚexpireszdict[str, object]Ú_tokenc                 óp   — t        «       } t        | j                  d«      xr | j                  d«      «      S )NÚTWITCH_CLIENT_IDÚTWITCH_CLIENT_SECRET)r   ÚboolÚget)Úes    r   Úhelix_configuredr(   5   s-   € Ü‹
€AÜ�—‘Ð(Ó)ÒK¨a¯e©eÐ4JÓ.KÓLÐLr   c                 ó*  — t        «       } | j                  d«      | j                  d«      }}|r|sy t        5  t        d   rEt	        j                  «       t        t        d   «      dz
  k  rt        t        d   «      cd d d «       S t        j                  j                  ||ddœ«      j                  «       }t        j                  j                  d|d	t        i¬
«      }t        j                  j                  |d¬«      5 }t        j                   |«      }d d d «       d   t        d<   t	        j                  «       t        |j                  dd«      «      z   t        d<   t        t        d   «      cd d d «       S # 1 sw Y   ŒgxY w# 1 sw Y   y xY w)Nr#   r$   r   r    é<   Úclient_credentials)Ú	client_idÚclient_secretÚ
grant_typez!https://id.twitch.tv/oauth2/tokenú
User-Agent)ÚdataÚheadersé   ©ÚtimeoutÚaccess_tokenÚ
expires_ini  )r   r&   Ú_token_lockr!   ÚtimeÚfloatÚstrÚurllibÚparseÚ	urlencodeÚencodeÚrequestÚRequestÚUAÚurlopenÚjsonÚload)r'   ÚcidÚsecr0   ÚreqÚrÚjs          r   Ú
_app_tokenrJ   :   sN  € Ü‹
€AØ�u‰uÐ'Ó(¨!¯%©%Ð0FÓ*Gˆ€CÙ‘CØÜ	ñ $Ü�'Š?œtŸy™y›{¬U´6¸)Ñ3DÓ-EÈÑ-JÒJÜ”v˜g‘Ó'÷$ñ $ô �|‰|×%Ñ%Ø°ÐCWÑXó
ç
‰&‹(ð 	ô �n‰n×$Ñ$Ð%HÈtÐ^jÔlnÐ]oÐ$ÓpˆÜ�^‰^×#Ñ# C°Ð#Ó4ð 	¸Ü—	‘	˜!“ˆA÷	à˜NÑ+Œˆw‰Ü ŸI™I›K¬%°·±°lÀDÓ0IÓ*JÑJŒˆyÑÜ”6˜'‘?Ó#÷$ñ $÷	ð 	ú÷$ð $ús,   ¸AF	ÂA:F	ÄE=ÄAF	Å=F	ÆF	Æ	Fc                óœ  — t        «       }|€yt        «       }t        j                  j	                  dt        j
                  j                  d| i«      z   |d   d|› �t        dœ¬«      }t        j                  j                  |d¬	«      5 }t        j                  |«      }ddd«       t        j                  d
«      «      S # 1 sw Y   Œ#xY w)u[   LIVE ã�ªã‚‰ Trueã€� ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ã�ªã‚‰ Falseã€‚ Helix æœªè¨­å®šã�ªã‚‰ None (= ä¸�æ˜Ž)ã€‚Nz$https://api.twitch.tv/helix/streams?Ú
user_loginr#   zBearer )z	Client-IdÚAuthorizationr/   )r1   r2   r3   r0   )rJ   r   r;   r?   r@   r<   r=   rA   rB   rC   rD   r%   r&   )ÚloginÚtokr'   rG   rH   rI   s         r   Úcheck_twitch_liverP   M   s¶   € ä
‹,€CØ
€{ØÜ‹
€AÜ
�.‰.×
 Ñ
 Ø.´·±×1GÑ1GÈÐW\ÐH]Ó1^Ñ^ØÐ 2Ñ3ÈÐPSÈuÀoÔegÑhð !ó €Cô 
�‰×	Ñ	 ¨RÐ	Ó	0ð °AÜ�I‰I�a‹Lˆ÷ä�—‘�f“ÓÐ÷ð ús   Â
CÃCc                ót   — i }| j                  d«      D ]!  }d|v sŒ|j                  dd«      \  }}|||<   Œ# |S )Nú;r   r   )r   )ÚrawÚtagsÚkvr   r   s        r   Ú_parse_tagsrV   ^   sI   € Ø€DØ�i‰i˜‹nò ˆØ�"Š9Ø—8‘8˜C Ó#‰DˆAˆqØˆD�ŠGðð €Kr   c           
     óê  — |s| S g }|j                  d«      D ]r  }d|vrŒ|j                  dd«      \  }}|j                  d«      D ]B  }d|v sŒ|j                  dd«      \  }}	 |j                  t        |«      t        |«      f«       ŒD Œt |s| S t	        | «      }	|D ]4  \  }}t        |t        |t        |	«      dz
  «      dz   «      D ]  }
d|	|
<   Œ	 Œ6 dj                  |	«      S # t        $ r Y Œ«w xY w)uT   emotes=25:0-4,12-16/1902:6-10 ã�®ä½�ç½® (code point å�˜ä½�) ã‚’æœ¬æ–‡ã�‹ã‚‰é™¤ã��ã€‚ú/ú:r   ú,ú-ú Ú )	r   ÚappendÚintÚ
ValueErrorÚlistÚrangeÚminÚlenÚjoin)ÚtextÚ
emotes_tagÚspansÚpartÚ_ÚrangesrH   ÚaÚbÚcharsÚis              r   Ú_strip_emotesrp   g   s  € áØˆØ#%€EØ× Ñ  Ó%ò 
ˆØ�d‰?ØØ—J‘J˜s AÓ&‰	ˆˆ6Ø—‘˜cÓ"ò 	ˆAØ�aŠxØ—w‘w˜s A“‘��1ðØ—L‘L¤# a£&¬#¨a«&Ð!1Õ2ñ		ð	
ñ ØˆÜ�‹J€EØò ‰ˆˆ1Ü�qœ#˜a¤ U£¨a¡Ó0°1Ñ4Ó5ò 	ˆAØˆE�!ŠHñ	ðð �7‰7�5‹>Ðøô "ò Ùðús   Á$%C&Ã&	C2Ã1C2c                  ó6   — e Zd ZdZdd„Zd	d„Zd
d„Zd	d„Zd„ Zy)Ú	IrcClientut   åŒ¿å�� (justinfan) ã�§ 1 channel ã�« JOIN ã�—ã�¦ PRIVMSG ã‚’ yield ã�™ã‚‹ã€‚ PING ã�«å¿œç­”ã€� åˆ‡æ–­æ™‚ã�¯ä¾‹å¤–ã€‚c                óh   — |j                  «       j                  d«      | _        d | _        d| _        y )Nr
   r   )ÚlowerÚlstriprN   ÚsockÚbuf)ÚselfrN   s     r   Ú__init__zIrcClient.__init__ƒ   s(   € Ø—[‘[“]×)Ñ)¨#Ó.ˆŒ
Ø*.ˆŒ	Øˆ�r   c                óÀ  — t        j                  t        t        fd¬«      }t	        j
                  «       j                  |t        ¬«      | _        | j                  j                  d«       d| _	        dt        j                  dd«      › �}| j                  d«       | j                  d	«       | j                  d
|› �«       | j                  d| j                  › �«       y )Nr2   r3   )Úserver_hostnamer   Ú	justinfani'  iŸ† z*CAP REQ :twitch.tv/tags twitch.tv/commandszPASS SCHMOOPIIEzNICK zJOIN #)ÚsocketÚcreate_connectionÚIRC_HOSTÚIRC_PORTÚsslÚcreate_default_contextÚwrap_socketrv   Ú
settimeoutÚidleÚrandomÚrandintÚ_sendrN   )rx   rS   Únicks      r   ÚconnectzIrcClient.connectˆ   s­   € Ü×&Ñ&¬´(Ð';ÀRÔHˆÜ×.Ñ.Ó0×<Ñ<¸SÔRZÐ<Ó[ˆŒ	Ø�	‰	×Ñ˜RÔ ØˆŒ	Øœ6Ÿ>™>¨%°Ó7Ð8Ð9ˆØ�
‰
Ð?Ô@Ø�
‰
�_Ô&Ø�
‰
�U˜4˜&�>Ô"Ø�
‰
�V˜DŸJ™J˜<Ð(Õ)r   c                óz   — | j                   €J ‚| j                   j                  |dz   j                  d«      «       y )Nz
r   )rv   Úsendallr>   )rx   r   s     r   rˆ   zIrcClient._send“   s3   € Ø�y‰yÐ$Ð$Ð$Ø�	‰	×Ñ˜4 &™=×0Ñ0°Ó9Õ:r   c                ó€   — | j                   �#	 | j                   j                  «        d | _         y y # t        $ r Y Œw xY w)N)rv   ÚcloseÚOSError)rx   s    r   rŽ   zIrcClient.close—   sA   € Ø�9‰9Ð ðØ—	‘	—‘Ô!ð ˆD�Ið !øô ò Ùðús   Ž1 ±	=¼=c              #  óp  K  — | j                   €J ‚	 	 | j                   j                  d«      }d| _        |st        d«      ‚| xj                  |z  c_        d	| j                  v �r{| j                  j                  d	d
«      \  }| _        |j                  dd¬«      }|j                  d«      r| j                  d|dd z   «       Œoi }|j                  d«      r#|d
d j                  dd
«      \  }}t        |«      }d|vr"d|j                  d«      dd v rt        d«      ‚ŒË|j                  d«      \  }}}|j                  d«      \  }}}|j                  d«      r|j                  d«      r|dd }t        ||j!                  dd«      «      }t#        |j!                  d«      xs  t#        t%        j$                  «       dz  «      «      }	|	|f–— d	| j                  v r�Œ{�ŒÑ# t        j                  t        f$ r7 | xj
                  dz  c_        | j
                  dkD  rt        d«      ‚d–— Y �Œ!w xY w­w) uD   (ts_ms, body) ã‚’ yieldã€‚ body ã�¯ emote é™¤åŽ»æ¸ˆã�¿ãƒ»æœª maskã€‚Ni   r2   ih  r…   )NNr   Úcloseds   
r   r   Úreplace)ÚerrorsÚPINGÚPONGé   ú@r\   z	 PRIVMSG Ú	RECONNECTé   zreconnect requestedz :zACTION úé   éÿÿÿÿÚemotesr]   ztmi-sent-tsiè  )rv   Úrecvr}   r4   ÚTimeoutErrorr…   ÚConnectionErrorrw   r   Údecoder   rˆ   rV   Ú	partitionÚendswithrp   r&   r_   r8   )
rx   Úchunkr   rf   rT   Útag_rawrj   ÚrestÚbodyÚtss
             r   ÚmessageszIrcClient.messagesŸ   s   è ø€ à�y‰yÐ$Ð$Ð$ØðØŸ	™	Ÿ™ uÓ-�ð ˆDŒIÙÜ% hÓ/Ð/Ø�HŠH˜Ñ�HØ˜TŸX™XÒ%Ø!%§¡§¡°¸Ó!;‘��d”hØ—{‘{ 7°9�{Ó=�Ø—?‘? 6Ô*Ø—J‘J˜v¨¨Q¨R¨Ñ0Ô1ØØ')�Ø—?‘? 3Ô'Ø$(¨¨ H§N¡N°3¸Ó$:‘M�G˜TÜ& wÓ/�DØ dÑ*Ø" d§j¡j°£o°b°qÐ&9Ñ9Ü-Ð.CÓDÐDØØ!Ÿ^™^¨KÓ8‘
��1�dØ!Ÿ^™^¨DÓ1‘
��1�dà—?‘? :Ô.°4·=±=ÀÔ3EØ  "˜:�DÜ$ T¨4¯8©8°H¸bÓ+AÓB�Ü˜Ÿ™ -Ó0ÒK´C¼¿	¹	»ÀdÑ8JÓ4KÓL�Ø˜$�h’ð+ ˜TŸX™XÓ%ñ øô —N‘N¤LÐ1ò Ø—	’	˜R‘•	Ø—9‘9˜s’?Ü)¨&Ó1Ð1Ø Ò Úðüs/   ‚H6“G# ®F1H6Ç!H6Ç#AH3È/H6È2H3È3H6N)rN   r:   ÚreturnÚNone)rª   r«   )r   r:   rª   r«   )	Ú__name__Ú
__module__Ú__qualname__Ú__doc__ry   rŠ   rˆ   rŽ   r©   © r   r   rr   rr   €   s   „ Ù~óó
	*ó;óó%r   rr   c           
     óD  — |rd|› d�nd}|dz  }	|rdnt        j                  |	«      }
d}|dkD  rt        j                  «       |z   nd}t        j                  «       }d}t        |› d| › d	|› d
|du› �d¬«       	 	 |�|j	                  «       rd}�n±|�t        j                  «       |k\  rd}�n”t        | «      }	 |j                  «        |j                  «       D �]#  \  }}|�t        t        |«      «      nd}|rc|rt        |› d|› �d¬«       n|
�|
j                  |||«       |dz  }|dz  dk(  r,|s*t        |› d|› d|
j                  › d|
j                  › �«       |�|j	                  «       r n�|�t        j                  «       |k\  r nt|€Œ¶t        j                  «       |z
  dk\  sŒÑt        j                  «       } |«       du sŒïd}|j                  «         |j                  «        |
�|
j                  «        yy |j                  «        |�|j	                  «       rd}n|�t        j                  «       |k\  rd}	 |
�|
j                  «        	 t        |› d|› d|› �«       y# t        t        t         f$ ru}t        |› d|›d�«       |j                  «        t#        d«      D ]+  }|�|j	                  «       r nt        j$                  d«       Œ- Y d}~|j                  «        �Œsd}~ww xY w# |j                  «        w xY w# |
�|
j                  «        w w xY w)uî   IRC ã�« JOIN ã�—ã�¦é›†ã‚�ã‚‹ã€‚ `still_live()` ã�Œ False ã‚’è¿”ã�—ã�Ÿã‚‰ (Helix ã�§é…�ä¿¡çµ‚äº†ã‚’æ¤œçŸ¥) é›¢è„±ã€‚
    Helix ã�Œç„¡ã�„æ™‚ã�¯ still_live=None ã�§å¸¸é§� (= åˆ‡æ–­ã�•ã‚Œã�Ÿã‚‰å†�æŽ¥ç¶š)ã€‚ duration_sec > 0 ã�§æ™‚é–“ä¸Šé™�ã€‚ú[z] r]   zcomments.sqliteNr   Ú
live_endedz[start] twitch #z dry=z helix=T)ÚflushÚstoppedÚduration_capz  r   éd   z  seen=z	 touched=z
 rejected=r*   Fz[irc] u    â†’ reconnect in 10sé
   z[done] reason=z seen=)r   Úsharedr8   ÚprintÚis_setrr   rŠ   r©   r   r   ÚwriteÚtouchedÚrejectedrŽ   r    r�   rŸ   rb   Úsleep)rN   Úout_dirÚ
stop_eventÚlog_tagÚgenreÚ
still_liveÚdryÚduration_secÚprefixÚdb_pathÚwriterÚcountÚdeadlineÚlast_live_checkÚstop_reasonÚclientr¨   rS   r§   r'   rj   s                        r   Úcollect_twitchrÏ   Ç   s  € ñ !(ˆq��	˜‰_¨R€FØÐ)Ñ)€GÙ‰Tœn×3Ñ3°GÓ<€FØ€EØ-9¸AÒ-=Œt�y‰y‹{˜\Ò)À4€HÜ—i‘i“k€OØ€KÜ	ˆVˆHÐ$ U G¨5°°°W¸ZÈtÐ=SÐ<TÐ
UÐ]aÕbð0ØØÐ%¨*×*;Ñ*;Ô*=Ø'�ÙØÐ#¬¯	©	«°xÒ(?Ø,�ÙÜ˜uÓ%ˆFðØ—‘Ô Ø%Ÿ™Ó0ó #‘G�B˜Ø36°?œ9¤T¨#£YÔ/È�DÙÙÜ! V H¨B¨t¨fÐ"5¸TÖBØ#Ð/Ø"ŸL™L¨¨T°5Ô9Ø ™
˜Ø  3™;¨!Ò+±CÜ! V H¨G°E°7¸)ÀFÇNÁNÐCSÐS]Ð^d×^mÑ^mÐ]nÐ"oÔpØ!Ð-°*×2CÑ2CÔ2EÙØÐ+´·	±	³¸xÒ0GÙØ!Ñ-´$·)±)³+ÀÑ2OÐSUÓ2UÜ*.¯)©)«+˜Ù%›<¨5Ò0Ø*6˜KØ"ŸL™LœNØ"ð —‘”ð ÐØ�L‰L�Nð ðI#ð: —‘”ØÐ%¨*×*;Ñ*;Ô*=Ø'‘ØÐ%¬$¯)©)«+¸Ò*AØ,�ØàÐØ�L‰L�NÜ	ˆVˆH�N ; -¨v°e°WÐ
=Õ>øô% $¤W¬lÐ;ò Ü˜˜  q eÐ+@ÐAÔBØ—‘”Ü˜r›ò "�AØ!Ð-°*×2CÑ2CÔ2EÙÜ—J‘J˜q•Mð"ó à—‘–ûðûð —‘•ûð ÐØ�L‰L�Nð úsi   Á:?L
 Â:CI) ÆI) Æ+I) Ç	I) ÇL
 È I) ÈAL
 É)K2É=AK-ËK5 ËL
 Ë-K2Ë2K5 Ë5LÌL
 Ì
Lc                 ó0  — dd l } | j                  «       }|j                  d«       |j                  dd¬«       |j                  dt        d¬«       |j                  dt	        t
        d	z  «      ¬
«       |j                  dd ¬
«       |j                  «       }t        «       r!t        dt        |j                  «      › �«       t        |j                  t        j                  |j                  «      |j                  |j                   |j"                  ¬«       y)Nr   rN   z--dryÚ
store_true)Úactionz
--duration)ÚtypeÚdefaultz	--out-dirr0   )rÔ   z--genrezhelix live: )rÅ   rÆ   rÃ   )ÚargparseÚArgumentParserÚadd_argumentr_   r:   r   Ú
parse_argsr(   rº   rP   rN   rÏ   ÚpathlibÚPathrÀ   rÅ   ÚdurationrÃ   )rÕ   Úaprl   s      r   ÚmainrÝ     sÈ   € Ûà	×	 Ñ	 Ó	"€BØ‡O�O�GÔØ‡O�O�G L€OÔ1Ø‡O�O�L¤s°A€OÔ6Ø‡O�O�K¬¬T°F©]Ó);€OÔ<Ø‡O�O�I t€OÔ,Ø
�‰‹€AÜÔÜ�Ô.¨q¯w©wÓ7Ð8Ð9Ô:Ü�1—7‘7œGŸL™L¨¯©Ó3¸¿¹ÈQÏZÉZÐ_`×_fÑ_fÕgØr   Ú__main__)rª   údict[str, str])rª   r%   )rª   ú
str | None)rN   r:   rª   zbool | None)rS   r:   rª   rß   )rf   r:   rg   r:   rª   r:   )Nr]   NNFr   )rN   r:   rÀ   zpathlib.PathrÁ   zthreading.Event | NonerÂ   r:   rÃ   rà   rÅ   r%   rÆ   r_   rª   r«   )rª   r_   ))r¯   Ú
__future__r   rC   rÙ   r†   r}   r�   ÚsysÚ	threadingr8   Úurllib.parser;   Úurllib.requestÚimport_youtube_liver   r   r   rÚ   Ú__file__ÚresolveÚparentr   r   r€   rA   r   r!   Ú__annotations__ÚLockr7   r(   rJ   rP   rV   rp   rr   rÏ   rÝ   r¬   Úexitr°   r   r   ú<module>rí      sF  ðò
õ #ã Û Û Û Û 
Û 
Û Û Û Û ç ?Ñ ?à€w‡|�|�HÓ×%Ñ%Ó'×.Ñ.×5Ñ5€Ø/Ñ €ˆ(ØM€óð  '+°sÑ;€ÐÓ ;Øˆi�n‰nÓ€óMó
$ó&ó"ó÷2Dñ DðT *.ØØØØØðE?ØðE?àðE?ð 'ðE?ð ð	E?ð
 ðE?ð 
ðE?ð ðE?ð 
óE?óPð  ˆzÒØ€C‡H�H‰T‹VÕð r   