+
    F.j                      a  R t0 t R t^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIHt ^ RIHt ^ RIt^ RIHtHtHtHtHtHtHtHt ^ RIHtHtHtHt ^ RI H!t! Rt"]#! ]$! ]%! R4      P                  4       4      4      t&]" R	]& 2t']" R
]& 2t(]" R]& 2t)Rt*Rt+Rt,Rt-Rt.Rt/Rt0Rt1Rt2Rt3^
t4^t5^t6Rt7Rt8Rt9Rt:RRRRRR/RRRR RR!/RR RR"RR#/RR"RR$RR%/RR$RR&RR'/RR&RRRR(/.t;R) R* lt<R+ R, lt=R- R. lt>R/ R0 lt?R1 R2 lt@]P                  ! R34      tB]BP                  P                  4        ]BP                  ]P                  4       R4]BnG        ]P                  ! R5R6R77      tI]! ],R8^R97      tJ]JP                  ]I4       ]BP                  ]J4       ]P                  ! 4       tN]NP                  ]I4       ]BP                  ]N4       R: R; ltORR< R= lltPRsQ] ^ k RsR] ^k RsS] ^k RsT] ^k RsU] ^k RsV] ^k ^ sW] ^k RsX] ^k RR> R? lltYR@ RA ltZRB t[^ s\RRC RD llt]RRE RF llt^RG RH lt_RI RJ lt`RK RL ltaRM RN ltbRO RP ltcRQ RR ltdRS RT lteRU RV ltfRW RX ltgRY RZ lthR[ R\ ltiR] R^ ltjR_ R` ltkRa Rb ltlRc Rd ltmRe Rf ltnRg Rh ltoRi Rj ltpRk Rl ltqRm Rn ltrRo Rp ltsRq Rr lttRs Rt ltuRu Rv ltvRw Rx ltwRy Rz ltxR{ R| ltyR} R~ ltzR R lt{Rt|. ROt}R R lt~RR R lltR R ltRR.R. RORRR.R. RORR./tR R ltR R ltR R ltR R ltR R ltR R ltRsRsRsRtR tR tR R ltR tR tR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R lt]EP4                  tRs] ^k Rs] ^	k R R ltR R ltR R ltR t^ RIt]EPB                  ! ]EPF                  ]4       ]EPB                  ! ]EPH                  ]4       R tR t]R8X  d   ]! 4        ]! 4        R# R# )zUTelegram bot for live monitoring and control of the sandman TV sleep pressure system.N)RotatingFileHandler)Path)Update
BotCommandInlineKeyboardButtonInlineKeyboardMarkup
WebAppInfoMenuButtonWebAppKeyboardButtonReplyKeyboardMarkup)ApplicationCommandHandlerCallbackQueryHandlerContextTypes)	ParseModez&https://a0d7b954-ssh.tail821319.ts.nettimez/panel.html?v=z/remote.html?v=z/status.html?v=z.8782685448:AAG2QRMgxr1WGc1J8VcQGwxmNmj7rDDDnBIz/share/.ha_cache/.ha_wd.logz/share/.ha_cache/.ha_notify.logz /share/.ha_cache/.ha_notify_chatz/share/.ha_cache/.ha_wd_tv_ipz/share/sandman.pyz/share/.ha_cache/wd_config.jsonz/share/sandman_config_test.jsonz$/share/.ha_cache/.ha_wd_events.jsonlz!/share/.ha_cache/.ha_wd_action_tsz"/share/.ha_cache/.ha_wd_state.jsonz"/share/.ha_cache/.ha_wd_speed.jsong~jth?gMb@?startz06:00endz18:00rate      ?z22:00g333333?22:30g333333?z00:00g?z02:00g      @g      @c                $    V ^8  d   QhR\         /#    returndict)formats   "/share/.ha_cache/ha_notify.py__annotate__r   A   s     z zD z    c                       \        \        4      ;_uu_ 4       p \        P                  ! V 4      uuR R R 4       #   + '       g   i     R # ; i  \         d    RRRR RR R^ R/ /u # i ; i)Ndeficit        last_updatedsession_starttotal_watch_today_min	daily_log)open
STATE_FILEjsonload	Exceptionfs    r   
read_stater/   A   s^    z*99Q<  z3otMdfgitvxyyzs+   A :
A A	A A A('A(c                $    V ^8  d   QhR\         /# )r   stater   )r   s   "r   r   r   I   s     & &t &r    c                     \        \        R 4      ;_uu_ 4       p\        P                  ! W^R7       RRR4       R#   + '       g   i     R# ; i)w)indentN)r(   r)   r*   dump)r1   r.   s   & r   write_stater6   I   s.    	j#		!		%1% 
			s	   <A	c                <    V ^8  d   QhR\         R\         R\        /# )r   hourminuter   )intfloat)r   s   "r   r   r   N   s!      c 3 5 r    c                   V ^<,          V,           p\          F  p\        \        VR,          P                  R4      4      w  rE\        \        VR,          P                  R4      4      w  rgV^<,          V,           pV^<,          V,           p	W8:  d
   V	R,          p	W(8  d   TMVR,           p
Yu;8:  d
   V	8  g   K  M K  VR,          u # 	  R# )z4Return the time-of-day multiplier for a given HH:MM.r   :r   r   r     )TIME_MULTIPLIERSmapr:   split)r8   r9   tentryshsmehemset_adjs   &&         r   _time_multiplier_atrK   N   s    r	FA!S%...s34S%,,,S12GbLGbL6LAVW>>>=  " r    c                0    V ^8  d   QhR\         R\        /# )r   minutesr   r;   str)r   s   "r   r   r   _   s      5 S r    c                t    \        V 4      ^<,          p\        V 4      ^<,          pV^ 8  d
   V RVR R2# V R2# )zFormat minutes as Xh Ym.zh 02dmr:   )rM   hrR   s   &  r   _fmt_durationrU   _   sE    GAGrA1uBqgQS7Nr    c                <    V ^8  d   QhR\         R\         R\        /# )r   r"   targetr   rN   )r   s   "r   r   r   h   s!       u  r    c                    W8:  d   R# V^ 8:  d   Rp\         P                  ! W,          4      ) \        ,          pV^<,          pV^8  d   R\        V4       R2# RVR R2# )zNEstimate hours to recover from deficit to target (rough, assumes no watching).nowgMbP?~rR   .0frT   )mathlogRECOVERY_RATEr:   )r"   rW   rM   hourss   &&  r   _estimate_recoveryr`   h   sd    {xx())M9GbLEqy3w<.""uSkr    sandman_botFz)%(asctime)s  %(levelname)-8s  %(message)sz%Y-%m-%d %H:%M:%S)datefmti )maxBytesbackupCountc                $    V ^8  d   QhR\         /# r   bool)r   s   "r   r   r      s       r    c                 "    \        \        4      ;_uu_ 4       p \        V P                  4       P	                  4       4      pRRR4       \
        P
                  ! 4       X,
          \        8  #   + '       g   i     L3; i  \         d     R# i ; i)z?Check if sandman executed an action within the suppress window.NF)r(   ACTION_MARKERr;   readstripr   ACTION_SUPPRESS_Sr,   )r.   tss     r   _sandman_action_recentrn      sc    -  Aqvvx~~'(B !		b $555 !   s(   A? (A,*A? ,A<	7A? ?BBc                <    V ^8  d   QhR\         R\         R\        /# )r   action
event_typer"   )rO   r;   )r   s   "r   r   r      s!      c s e r    c                   ^ RI pVP                   P                  4       pRVP                  R4      RVP                  R4      RV RV/pVe   \        V^4      VR&    \	        \
        R	4      ;_uu_ 4       pVP                  \        P                  ! V4      R
,           4       RRR4       R#   + '       g   i     R# ; i  \         d     R# i ; i)z:Append a structured event to the shared events JSONL file.Ndatez%Y-%m-%dr   z%H:%Mrp   typer"   a
)
datetimerY   strftimeroundr(   EVENTS_FILEwriter*   dumpsr,   )rp   rq   r"   _dtrY   rC   r.   s   &&&    r   	log_eventr~      s    
,,


CS\\*-vs||G7Lvvz3E !,i+s##qGGDJJu%,- $### s0   B> 3-B* B> *B;	5B> ;B> >CCc                >    V ^8  d   QhR\         R\        R,          /# )r   force_rediscoverr   N)rg   rO   )r   s   "r   r   r      s       t r    c                   \         '       d   V '       g   \         # V '       gp   \        P                  P                  \        4      '       dG   \        \        4      P                  4       P                  4       pV'       d   Vs V\        n	        \         # \        P                  ! 4       s \         '       d2   \        \        4      P                  \         4       \         \        n	        \         # )z.Return cached TV IP, re-discovering if needed.)tv_ipospathexistsTV_IP_CACHEr   	read_textrk   
tv_controlTV_IPdiscover_tv
write_text)r   cacheds   & r   	get_tv_ipr      s     u%{ ; ;k",,.446E%JL""$Eu[$$U+ 
Lr    c                2    V ^8  d   QhR\         R,          /# r   r   NrO   )r   s   "r   r   r      s      S4Z r    c                 2   \         '       d   \         # \        P                  P                  \        4      '       dG   \        \        4      P                  4       P                  4       p V '       d   V s V \        n	        \         # Rs \         \        n	        \         # )zHReturn TV IP, preferring cache even if TV is off (for standby commands).z192.168.1.50)
r   r   r   r   r   r   r   rk   r   r   )r   s    r   get_tv_ip_or_cachedr      sd     u	ww~~k""k",,.446E%JLEJLr    c                  d    R s  \        P                  ! \        4       R #   \         d     R # i ; iN)r   r   remover   FileNotFoundError r    r   invalidate_tv_ipr      s*    E
		+ s     //c                J    V ^8  d   QhR\         R\        R\        R,          /# )r   r   timeoutr   N)rO   r;   r   )r   s   "r   r   r      s%       u TD[ r    c                 j   \        4       pV'       g   R # \        P                  ! WVR7      pVf   \        ^,          s\        ^
8  dg   \        P                  ! 4       pV'       dH   WB8w  dB   VsV\        n        \        \        4      P                  V4       \        P                  RW$4       ^ sV# ^ sV# )Nr   ipzTV IP changed: %s -> %s)r   r   js_get_consecutive_failuresr   r   r   r   r   r   r]   info)r   r   r   resultnew_ips   &&   r   r   r      s    		Bt<F~" B&++-F&,#)
 [!,,V42B?$%! M !"Mr    c                H    V ^8  d   QhR\         R\        R\        R\        /# )r   r   datar   r   )rO   r   r;   rg   )r   s   "r   r   r      s.     B B# BT BE B$ Br    c                 Z    \        4       pV'       g   R # \        P                  ! WW#R7      # )Fr   )r   r   js_post)r   r   r   r   s   &&& r   r   r      s#    		Bd'AAr    c                0    V ^8  d   QhR\         R\        /# )r   keyr   rO   rg   )r   s   "r   r   r      s     ) )# )$ )r    c                 D    \        4       p\        P                  ! WR 7      # r   )r   r   js_key)r   r   s   & r   send_keyr      s    		BS((r    c                2    V ^8  d   QhR\         R,          /# r   rS   )r   s   "r   r   r      s     ( (t (r    c                  D    \        4       p \        P                  ! V R 7      # r   )r   r   
get_volumer   s    r   upnp_get_volumer      s    		B  B''r    c                $    V ^8  d   QhR\         /# )r   volrS   )r   s   "r   r   r     s     & & &r    c                 H    \        4       p\        P                  ! WR 7       R# r   N)r   r   
set_volume)r   r   s   & r   upnp_set_volumer     s    		B#%r    c                $    V ^8  d   QhR\         /# )r   mutedrf   )r   s   "r   r   r   
  s     & & &r    c                 H    \        4       p\        P                  ! WR 7       R# r   )r   r   set_mute)r   r   s   & r   upnp_set_muter   
  s    		B%r    c                2    V ^8  d   QhR\         R,          /# r   rS   )r   s   "r   r   r     s      C$J r    c                      \         P                  P                  \        4      '       d6    \	        \        \        4      P                  4       P                  4       4      # R #   \         d     R # i ; ir   )	r   r   r   CHAT_ID_FILEr:   r   r   rk   r,   r   r    r   
load_ownerr     sX    	ww~~l##	tL)335;;=>>   		s   3A! !A0/A0c                $    V ^8  d   QhR\         /# )r   chat_idrS   )r   s   "r   r   r     s     1 1 1r    c                     V s \        \        4      P                  \	        V 4      4       \
        P                  R V 4       R# )zOwner chat_id saved: %dN)owner_chat_idr   r   r   rO   r]   r   )r   s   &r   
save_ownerr     s-    M!!#g,/HH&0r    c                0    V ^8  d   QhR\         R\        /# )r   updater   )r   rg   )r   s   "r   r   r   "  s     5 5V 5 5r    c                     \         f   \        4       s \         f"   \        V P                  P                  4       R# V P                  P                  \         8H  # )NT)r   r   r   effective_chatid)r   s   &r   is_ownerr   "  sC    "6((++,  ##}44r    c                2    V ^8  d   QhR\         R,          /# r   rS   )r   s   "r   r   r   /  s      #* r    c                  F    \         P                  ! R R.RR7      p \        P                  ! 4       pV P	                  4        FJ  pRV9   g   K  RV9  g   K  RV9  g   K  VP                  4       p\        V^,          4      pWA8w  g   KH  Vu # 	  R#   \         d     R# i ; i)psauxT)textz
sandman.pyra   grepN)
subprocesscheck_outputr   getpid
splitlinesrA   r:   r,   )outmy_pidlinepartspids        r   find_sandman_pidr   /  s    
%%tUm$?NN$Dt#T(AfTXFX

%(m=J %   s0   AB B B %B 	B B B B c                $    V ^8  d   QhR\         /# r   rf   )r   s   "r   r   r   >  s     	 	d 	r    c                      \        4       p V '       d=    \        P                  ! R \        V 4      .RR7       \        P                  RV 4       R# R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)killT)checkzKilled sandman PID %dzFailed to kill sandman: %sNF)r   r   runrO   r]   r   r,   error)r   rI   s     r   kill_sandmanr   >  sg    

C
	7NNFCH-T:HH,c2   	7II2A66	7s   9A A=A88A=c                0    V ^8  d   QhR\         R\        /# )r   configr   r   )r   s   "r   r   r   J  s      # $ r    c                 (    \         P                  ! R \        RV RR.R\         P                  \         P                  R7      p\        P                  RVP                  V 4       R#   \         d"   p\        P                  RT4        Rp?R	# Rp?ii ; i)
python3z--configz	--log-dirz/share/Tstart_new_sessionstdoutstderrz%Started sandman PID %d with config %szFailed to start sandman: %sNF)	r   PopenSANDMAN_SCRIPTDEVNULLr]   r   r   r,   r   )r   procrI   s   &  r   start_sandmanr   J  sx    
FKS"%%%%	
 	8$((FK 		/3s   A!A% %B0BBc                $    V ^8  d   QhR\         /# r   rf   )r   s   "r   r   r   \  s      t r    c                       \         P                  ! R R.RRR7       R#   \        \         P                  3 d     R# i ; i)adbversionT)capture_outputr   F)r   r   r   CalledProcessErrorr   r    r   adb_availabler   \  s<    y)$dKz<<= s    >>c                $    V ^8  d   QhR\         /# r   )tuple)r   s   "r   r   r   d  s     1 1U 1r    c                  D    \        4       p \        P                  ! V R 7      # r   )r   r   adb_get_current_appr   s    r   r   r   d  s    		B))R00r    c                2    V ^8  d   QhR\         R,          /# r   r   )r   s   "r   r   r   i  s     , ,t ,r    c                  h    \        4       '       g   R # \        4       p \        P                  ! V R7      # )Nr   )r   r   r   adb_screenshotr   s    r   take_screenshotr   i  s%    ??		B$$++r    c                0    V ^8  d   QhR\         R\         /# )r   r   r   r   )r   s   "r   r   r   r  s      s s r    c                    \         P                  ! RV 4      pV'       dI   VP                  ^4      VP                  ^4      VP                  ^4      rCpVR8X  d	   RV RV 2# V RV 2# V P                  4       # )zFormat a sandman log line for Telegram readability.
Input:  '2026-03-20 23:09:11 [INFO]    Grace period: 54 min ...'
Output: '23:09 Grace period: 54 min ...'
z6\d{4}-\d{2}-\d{2} (\d{2}:\d{2}):\d{2} \[(\w+)\]\s*(.*)WARNINGu   ⚠️  )rematchgrouprk   )r   rR   rB   levelmsgs   &    r   fmt_log_liner	  r  sr     	JDQA
AGGAJ
#IQCq&&AcU|::<r    c                D    V ^8  d   QhR\         R\        P                  /# r   r   ctxr   r   DEFAULT_TYPE)r   s   "r   r   r     s       F )B)B r    c                    "   \        V 4      '       g   R # V P                  P                  R\        P                  R7      G R j  xL
  R #  L5i)Nuq   🌙 *Sandman Bot*

Use the command menu (tap /) for all controls.
Log updates will be pushed here automatically.
parse_mode)r   message
reply_textr   MARKDOWN)r   r  s   &&r   	cmd_startr    sD     F
..
#
#	9 %%	 $   s   AAAAc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     ~* ~*V ~*,*C*C ~*r    c           
        "   \        V 4      '       g   R # V P                  P                  R4      G R j  xL
 p\        4       p. pV'       g   VP	                  R4       EM\\        R4      pV'       d   VP                  RR4      MRpVR8X  d   RM
VR8X  d   R	MR
pVP	                  V RV 24       VP	                  RV 24       \        R4      pV'       dZ   VP                  RR4      p	VP                  RR4      p
VP                  R4      '       d   RMRpVP	                  V RV	 RV
 24       M#\        4       pVe   VP	                  RV R24       \        4       w  rpV'       d   \        P                  W4      pV'       d   RV R2MRpVP	                  RV V 24       V'       d   VP                  R4      '       dH   RVR,           2pVP                  R4      '       d   VRVR,           2,          pVP	                  V4       VP                  R4      '       d/   \        VR,          ^<4      w  ppVP	                  R V R!VR" 24       Ml\        R#4      pV'       dZ   VP                  R$/ 4      P                  R%R4      pV'       d0   VR&8w  d)   VP	                  R\        P                  VV4       24       \        4       pV'       Ed    \        P                  ! R'R(.R)R)^R*7      pR+VP                  9   d   R,MR-p^ R IpRpRp\!        \"        R.4      ;_uu_ 4       pVP%                  4       RUR   F  pR/V9   d4   VP'                  R0V4      pV'       d   R1VP)                  ^4       R22pK;  K=  R3V9   g   KF  VP'                  R4V4      pV'       g   Kb  R5VP)                  ^4       R6VP)                  ^4       R2pK  	  R R R 4       ^ R Ip V P*                  P-                  4       p!VR,8X  d   \.        M\0        p"\!        V"4      ;_uu_ 4       p\2        P4                  ! V4      p#R R R 4       X#P                  R7R84      p$V#P                  R9R:4      p%V#P                  R;^<4      p&\7        \8        V$P;                  R!4      4      w  p'p(V!P=                  V'V(^ R<7      p)V!P>                  ^8  d+   V'^8  d$   V)P=                  V)P@                  ^,
          R=7      p)V!V)8  g   V!P>                  ^8  dK   \9        V!V),
          PC                  4       ^<,          4      RV,          p*VP	                  R>V$ R?V% R@V* RA24       MB\9        V)V!,
          PC                  4       ^<,          4      p+VP	                  R>V$ R?V% RV+ RB24       VP	                  RCV RDV R24       V'       d   VP	                  V4       V'       d   VP	                  V4       MVP	                  RF4       \        P                  ! . RWOR)R)RG7      p,RHV,P                  9   d   VP	                  RI4       \F        PH                  PK                  RJ4      p-V-'       d   VP	                  RK4       \M        4       p.V.P                  RLRM4      p/^ p0V.P                  RN4      '       d    X P*                  PO                  V.RN,          4      p1V P*                  P-                  V1PP                  ;'       g    V PR                  PT                  4      V1,
          PC                  4       ^<,          p0V.P                  RO^ 4      p2VP	                  RPV/RQ RR\W        V04       RS\W        V24       24       VPY                  RTP[                  V4      4      G R j  xL
  R #  EL5  + '       g   i     EL; i  + '       g   i     ELd; i  \D         d    TP	                  RET R24        ELi ; i  \D         d     Li ; i Li5i)XNu   ⏳ Checking...u&   📺 TV IP: unknown (never discovered)
powerstate?unreachableOn   🟢Standbyu   🔴   ❓z Power: u	   🌐 IP: audio/volumecurrentmaxr   u   🔇u   🔊z	 Volume: /   🔊 Volume: z (UPnP) () u
   📱 App: title   🎬 artist    — 
position_su   ⏱ r=   rQ   zactivities/current	componentpackageNameNAr   r   Tr   r   r   config_testtestprodrzGrace periodu$   Grace period — (\d+) min remainingu   ⏳ Grace: z min remainingzsev=z#sev=([\d.]+) max_sev=(\d+) \| (\S+)u   ⚡ Last action: z (sev bad_hours_startr   bad_hours_peakz00:30grace_period_minutes)r8   r9   second)dayu   🌙 Bad hours:    →u	    (IN — zm in)zm until)u   🤖 Sandman: z mode (PID u   🤖 Sandman: running (PID u   🤖 Sandman: stoppedr   r   htbu   🌐 Throttle: ON#/share/.ha_cache/.netmon_state.jsonu   🔀 MITM: activer"   r#   r%   r&      🧠 Deficit: .2fz | Session: 
 | Today: rv   ir>   tcqdiscshowdevend0).r   r  r  r   appendr   getr   r   	APP_NAMESdivmodr   r   r   r   r  r(   LOG_FILE	readlinessearchr  rw   rY   TEST_CONFIGPROD_CONFIGr*   r+   r@   r:   rA   replacer8   r8  total_secondsr,   r   r   r   r/   fromisoformattzinfotimezoneutcrU   	edit_textjoin)3r   r  r  r   linespwpowericonvol_datacurmxr   uvpkgplaybackextrasfriendlystatus
title_lineminssecsactpr   r3  config_name_re
grace_infoseverity_infor.   r   rR   rw   rY   config_pathcfg	bad_startbad_peak	grace_minbhbmbad_timemins_in
mins_untiltc_checkmitm_runningr1   dsession_minss	today_mins3   &&                                                 r   
cmd_statusr}    s    F))*;<
<C		BE=>L!-/|S)]$vey6HFevXeW-.y%&.),,y#.CeS)B&ll733FELLE7)C5"67 "B~}RD89 3 5v }}S.H)1r(1%rFLL:hZx89::g&&#((9!:Jzz(++"fX.>-?&@@
LL,::l++!'|(<b!AJD$LL4vQtCj!9: -.CGGK,00CdLL:immAq.A-B!CD 
C
s1	?e}TVWXA$1QXX$=&6K JMh$$KKM#$/D%-JJ'NPTU+6qwwqzl.)QJ 4JJ'MtT1.?
|6RSRYRYZ[R\Q]]^,_M 0 % ##'')C)4)>+KKk""aiil # 17;Iww/9H 6;Iiooc23FB{{2a{@Hxx"}b#++q0@+Ah#((Q,sX~<<>CDP/	{#hZyQXPYY^_` (S.!?!?!AB!FG
/	{#hZr*U]^_LL>+k#aHIZ(]+ 	,- ~~DUY`deH()77>>"GHL() LE		)S!AKyy!!	""001GHB#,,001S1Sh>O>O>S>STWYYhhjmooK 		115I	LL>!C];5O4PPZ[hir[sZtuv
--		%(
)))w =t %$$  #"".  	?LL6se1=>	?*  		
 *s   2_])C_B_,_"_&:_!A+_A_A^ !7],],;],+],A^ ^ %E2^ ^ 1^ A5_8A_?A	^; 	7^;  A"_"_#_,]=	7	^  ^		^  ^84_7^88_;_	__		_c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     O O Ol.G.G Or    c                   "   \        V 4      '       g   R # \        4       '       g&   V P                  P                  R4      G R j  xL
  R # V P                  P                  R4      G R j  xL
 p\        P
                  ! \        4      G R j  xL
 pV'       d   \        P                  P                  V4      '       dm   \        P                  P                  V4      ^ 8  dI   V P                  P                  \        VR4      R7      G R j  xL
  VP                  4       G R j  xL
  R # VP                  R4      G R j  xL
  R #  L L L L? L) L5i)Nu   ❌ ADB not installedu   📸 Taking screenshot...rb)photou/   ❌ Screenshot failed — TV off or DRM content)r   r   r  r  asyncio	to_threadr   r   r   r   getsizereply_photor(   deleterU  )r   r  r  r   s   &&  r   cmd_screenshotr    s     F??nn''(?@@@))*EF
FC""?33Dt$$)>)Bnn((tD$/?(@@@jjlmmMNNN 	A
F3@Nsy   AEE$E)E*!EEE$E=AE
E
E"E#E=E>EEE
EEEc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s      	> 	>6 	>(A(A 	>r    c                   a"   \        V 4      '       g   R # \        P                  ! R 4      G R j  xL
 pV'       dj   VP                  RR4      '       * o\        P                  ! V3R l4      G R j  xL
  V P                  P                  S'       d   RMR 4      G R j  xL
  R # V P                  P                  R4      G R j  xL
  R #  L L[ L/ L5i)Nc                      \        R 4      # r  r   r   r    r   <lambda>cmd_mute.<locals>.<lambda>"  s	    vn/Er    r   Fc                     < \        S 4      # r   )r   )new_mutes   r   r  r  %  s
    h(?r    
   🔇 Muted   🔊 Unmutedu   ❌ Can't reach TV)r   r  r  rG  r  r  )r   r  r[  r  s   && @r   cmd_muter    s     F&&'EFFH||GU33 ?@@@nn''8<*XZZZnn''(<=== G 	AZ=sP   .CCC4C2C3 CC C!$CCCCCCc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   +  s      > >V >,*C*C >r    c           
        a"   \        V 4      '       g   R # VP                  pV'       d   V^ ,          P                  4       '       g   \        P                  ! R 4      G R j  xL
 pV'       dL   V P
                  P                  RVP                  RR4       RVP                  RR4       24      G R j  xL
  R # V P
                  P                  R4      G R j  xL
  R # \        ^<\        ^ \        V^ ,          4      4      4      o\        P                  ! V3R l4      G R j  xL
  V P
                  P                  R	S 24      G R j  xL
  R #  L L Lz L2 L5i)
Nc                      \        R 4      # r  r  r   r    r   r  cmd_volume.<locals>.<lambda>0  s	    6.3Ir    r#  r   r  r"  r!  zUsage: /volume N (0-60)c                     < \        S 4      # r   )r   )r   s   r   r  r  7  s
    OC$8r    u   🔊 Volume → )r   argsisdigitr  r  r  r  rG  minr!  r:   )r   r  r  r[  r   s   &&  @r   
cmd_volumer  +  s    F88DtAw(( **+IJJ..++mHLLTW<X;YYZ[c[g[ghmor[sZt,uvvv 	 ..++,EFFF
b#aT!W&
'C


8
999
..
#
#&6se$<
=== KvF :=sl   'EEE"E#E/AE3E4$EEA	E"E#%EE	EEEEEc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   ;  s$     J JF J)B)B Jr    c                    "   \        V 4      '       g   R # \        P                  ! R 4      G R j  xL
 pV P                  P	                  V'       d   RMR4      G R j  xL
  R #  L3 L5i)Nc                      \        R 4      # )Pauser   r   r    r   r  cmd_pause.<locals>.<lambda>>  s	    '):r    u
   ⏸ Paused
   ❌ Failedr   r  r  r  r  r   r  oks   && r   	cmd_pauser  ;  sK     F  !:;	;B
..
#
#BLL
III 
<I'   .A(A$ A(A(A&A(&A(c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   B  s$     N N6 N(A(A Nr    c                    "   \        V 4      '       g   R # \        P                  ! R 4      G R j  xL
 pV P                  P	                  V'       d   RMR4      G R j  xL
  R #  L3 L5i)Nc                      \        R 4      # )Playr  r   r    r   r  cmd_play.<locals>.<lambda>E  	    &)9r    u   ▶️ Playingr  r  r  s   && r   cmd_playr  B  sL     F  !9:	:B
..
#
#$4
MMM 
;Mr  c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   I  s$     I I6 I(A(A Ir    c                    "   \        V 4      '       g   R # \        P                  ! R 4      G R j  xL
 pV P                  P	                  V'       d   RMR4      G R j  xL
  R #  L3 L5i)Nc                      \        R 4      # )Homer  r   r    r   r  cmd_home.<locals>.<lambda>L  r  r    u	   🏠 Homer  r  r  s   && r   cmd_homer  I  sK     F  !9:	:B
..
#
#2K<
HHH 
;Hr  c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   P  s$     w wF w)B)B wr    c                 F  "   \        V 4      '       g   R # \        4       pV'       g&   V P                  P                  R4      G R j  xL
  R # \        P
                  ! R 4      G R j  xL
 pV P                  P                  V'       d   RMR4      G R j  xL
  R #  LT L5 L
5i)NuD   ❌ TV IP unknown — turn TV on manually first so I can discover itc                      \        R 4      # )r  r  r   r    r   r  cmd_power.<locals>.<lambda>W  s	    ))<r    u   ⚡ Power toggledu0   ❌ Failed — TV may be fully off (not standby))r   r   r  r  r  r  )r   r  r   r  s   &&  r   	cmd_powerr  P  sx     F		Bnn''(nooo  !<=	=B
..
#
#2$7Cu
vvv 	p	=vs<   AB!B B!'B( B!	B!BB!B!B!c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   [  s$     q q6 q(A(A qr    c                  "   \        V 4      '       g   R# \        4       pRVR&   \        P                  P                  4       P	                  4       VR&   \        V4       V P                  P                  R4      G Rj  xL
  R#  L5i)z;Quick test: set deficit to 0.7 for immediate heavy actions.Nffffff?r"   r$   uH   ⚡ Test mode — deficit set to 0.70
Heavy actions should trigger soon.)r   r/   rw   rY   	isoformatr6   r  r  )r   r  r1   s   && r   cmd_testr  [  sg     FLEE)$--113==?E.
..
#
#$o
ppps   A;B=B>Bc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   f  s$     T T6 T(A(A Tr    c                 :  "   \        V 4      '       g   R # V P                  P                  R4      G R j  xL
 p\        4        \        P
                  ! ^4      G R j  xL
  \        \        4      pTP                  V'       d   RMR4      G R j  xL
  R #  L` L: L
5i)Nu   ⏳ Switching to production...u   🌙 Production mode ON   ❌ Failed to start)	r   r  r  r   r  sleepr   rN  rU  )r   r  r  r  s   &&  r   cmd_prodr  f  sp     F))*JK
KCN
--
	{	#B
--R1=R
SSS	 LSs3   2BB'BB1BBBBBc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   p  s$     g g6 g(A(A gr    c                    "   \        V 4      '       g   R # \        4       pV P                  P                  V'       d   RMR4      G R j  xL
  R #  L5i)Nu   🛑 Sandman stoppedu   ℹ️ Sandman wasn't running)r   r   r  r  r  s   && r   cmd_stopr  p  s8     F	B
..
#
#b$:Fe
fffs   AAA	Ac                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   w  s$     \ \F \1J1J \r    c                   "   \        V 4      '       g   R # \        4       '       d&   V P                  P                  R4      G R j  xL
  R # \	        \
        4      pV P                  P                  V'       d   RMR4      G R j  xL
  R #  LD L5i)Nu   ℹ️ Sandman already runningu   ✅ Sandman startedr  )r   r   r  r  r   rN  r  s   && r   cmd_start_sandmanr  w  sl     Fnn''(HIII	{	#B
..
#
#R$9EZ
[[[ 	J \s$   ABB	=BBBBc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s      ; ;& ;|'@'@ ;r    c                   "   \        V 4      '       g   R # \        P                  P                  \        4      '       g&   V P
                  P                  R4      G R j  xL
  R #  \        \        R4      ;_uu_ 4       pVP                  4       pR R R 4       ^pVP                  '       dN   VP                  ^ ,          P                  4       '       d'   \        ^2\        VP                  ^ ,          4      4      p\        X4      V8  d   W4) R  MTpV Uu. uF%  qfP                  4       '       g   K  \        V4      NK'  	  ppRP!                  V4      pV'       g   Rp\        V4      R8  d   VRR  pV P
                  P                  V4      G R j  xL
  R #  EL7  + '       g   i     EL; iu upi  L"  \"         d3   p	T P
                  P                  RT	 24      G R j  xL 
   R p	?	R # R p	?	ii ; i5i)Nu   ℹ️ No log filer3  rv   z(empty)  u   ❌ Error: i`)r   r   r   r   rJ  r  r  r(   rK  r  r  r  r:   lenrk   r	  rV  r,   )
r   r  r.   rW  ntaill	formattedr   rI   s
   &&        r   cmd_logr    sh    F77>>(##nn''(<===;(C  AKKME !888++--BCHHQK()A Z1_uRSz%.2@dggi_\!_d	@yy#Dt9t<Dnn''--- 	> !   A 	. ;nn''+aS(9:::;s   <G2G2FG2%F2 =FF2 *&F2 AF2 F+.F+=AF2 F0F2 G2F(	"F2 2G/=!G*G!G*$G2*G//G2c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     %N %N6 %N(A(A %Nr    c                  "   \        V 4      '       g   R# VP                  p\        P                  P	                  R4      pV'       g4   V P
                  P                  RV'       d   RMR R24      G Rj  xL
  R# V^ ,          P                  4       pVR8X  Ed   V'       d&   V P
                  P                  R4      G Rj  xL
  R# V P
                  P                  R	4      G Rj  xL
 p\        P                  ! R
R.R\        P                  \        P                  R7       \        P                  ! ^4      G Rj  xL
  \        P                  P	                  R4      '       d   VP                  R4      G Rj  xL
  R# VP                  R4      G Rj  xL
  R# VR8X  d   V'       g&   V P
                  P                  R4      G Rj  xL
  R# \        P                  ! 4        \!        4        \        P"                  ! . RORR7       \        P                  ! ^4      G Rj  xL
  V P
                  P                  R4      G Rj  xL
  R# R#  EL EL ELn EL L L L L= L5i)u/   Control ARP MITM daemon — /mitm on|off|statusNr<  u   🔀 MITM: ONOFFuQ   

/mitm on — start (needed for throttle)
/mitm off — stop and restore networkonu   ℹ️ MITM already runningu   ⏳ Starting MITM...r   /share/.ha_cache/netmon.pyTr   u,   🔀 MITM ON — TV traffic flows through Piu   ❌ MITM failed to startoffu   ℹ️ MITM not runningr   u"   🔀 MITM OFF — network restored)pkillz-fzarp_mitm.py)r   r  r   r   r   r  r  lowerr   r   r   r  r  rU  r   throttle_remove_clear_throttle_stater   )r   r  r  runningargr  s   &&    r   cmd_mitmr    s    F88DggnnBCGnn'''$u5 65 56 	6 	6 	
q'--/C
d{..++,IJJJNN--.DEE)%ABVZ * 2 2:;M;M	OmmA77>>?@@-- NOOO-- :;;;	..++,EFFF""$5dKmmAnn''(LMMM 
'	6 KE 	O; G 	Ms   AI$	I$'I$5I6,I$#I$I$I$&I'AI$<I=(I$&I$:I;I$II$+I$	I
AI$"I #"I$I"	I$I$I$I$I$I$I$ I$"I$z/share/device_names.jsonswitch.sonoff_10020b052f_1switch.sonoff_10020b052f_2switch.sonoff_10020b0c7c_1-switch.magic_switch_s1e_595a_kitchen_switch_1c                $    V ^8  d   QhR\         /# r   r   )r   s   "r   r   r     s      4 r    c                       \        \        4      ;_uu_ 4       p \        P                  ! V 4      uuR R R 4       #   + '       g   i     R # ; i  \         d    / u # i ; ir   )r(   DEVICE_NAMES_FILEr*   r+   r,   r-   s    r   _get_device_namesr    sB    #$$99Q< %$$$ 	s+   A :
A A	A A AAc          	      V    V ^8  d   QhR\         R\         R\        R\        R,          /# )r   methodr   r   r   N)rO   r   )r   s   "r   r   r     s,     1 1C 1s 1$ 1$+ 1r    c                 0    \         P                  ! WV4      # r   )r   ha_api)r  r   r   s   &&&r   _ha_apir    s    V400r    c                $    V ^8  d   QhR\         /# r   )list)r   s   "r   r   r     s     	 	t 	r    c                     \        4       p \        P                  ! \        4      p. pV F*  w  r4pV P	                  W44      pVP                  W6V34       K,  	  V# )z0Returns [(entity_id, friendly_name, state), ...])r  r   ha_get_device_states
HA_DEVICESrG  rF  )namesrawr   eiddefault_namer1   names          r   _get_ha_device_statesr    sT    E

)
)*
5CF$' 5yy+s%() %( Mr    u   🛏 Bedroomu   🛋 Living Roomu   🍽 Diningu   🍳 Kitchenu   🧺 Laundryc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s       f <+D+D r    c                   "   \        V 4      '       g   R# \        R\        \        R7      R7      ..pV P                  P                  R\        V4      R7      G Rj  xL
  R#  L5i)zOpen devices Mini App.Nu   🏠 Open Devicesurlweb_appTap to open:reply_markup)r   r   r   
WEBAPP_URLr  r  r   r   r  keyboards   && r   cmd_devicesr    s`     F%z*  H ..
#
#)(3 $      AA%A#A%c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s        l&?&? r    c                   "   \        V 4      '       g   R# \        R\        \        R7      R7      ..pV P                  P                  R\        V4      R7      G Rj  xL
  R#  L5i)zOpen TV remote Mini App.Nu   📺 Open TV Remoter  r  r  r  )r   r   r   TV_REMOTE_URLr  r  r   r  s   && r   cmd_tvr    s`     F%}-  H ..
#
#)(3 $   r  c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   #  s       & |/H/H r    c                   "   \        V 4      '       g   R# \        R\        \        R7      R7      ..pV P                  P                  R\        V4      R7      G Rj  xL
  R#  L5i)z$Open Sandman observability Mini App.Nu   🌙 Open Sandmanr  r  r  r  )r   r   r   SANDMAN_APP_URLr  r  r   r  s   && r   cmd_sandman_appr  #  s`     F%/  H ..
#
#)(3 $   r  c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   1  s      " "& "|/H/H "r    c                  aaa"   V P                   pV'       d"   VP                  P                  R4      '       g   R# VP                  4       G Rj  xL
  VP                  R,          oSP	                  R4      ^ ,          o\
        P                  ! V3R l4      G Rj  xL
 pV'       g   VP                  R4      G Rj  xL
  R# VP                  RR4      pVR	8X  d   R
MRo\
        P                  ! VVV3R l4      G Rj  xL
  \
        P                  ! R4      G Rj  xL
  \
        P                  ! \        4      G Rj  xL
 p. pV F?  w  rxp	V	R	8X  d   RM
V	R8X  d   RMRp
VP                  \        V
 RV 2RV 2R7      .4       KA  	  VP                  R\        V4      R7      G Rj  xL
  R#  ELc EL L L L L} L5i)z"Handle device toggle button press.zdev:N:   NN.c                  "   < \        R RS  24      # )GETz/states/r  )r  s   r   r  !device_callback.<locals>.<lambda><  s    (3%@P1Qr    u   ❌ Device unreachabler1   r  r  turn_offturn_onc                  .   < \        R RS  RS 2RS/4      # )POSTz
/services/r"  	entity_idr  )domainr  services   r   r  r  C  s"    GFj'4SVacfUg$hr    g      ?r  u   ⚫r  r  )callback_datau   🏠 Devices — tap to toggler  )callback_queryr   
startswithanswerrA   r  r  edit_message_textrG  r  r  rF  r   r   )r   r  query
state_datar   devicesr  d_eidr  r1   rZ  r  r  r  s   &&         @@@r   device_callbackr)  1  s    !!E

--f55
,,.
**R.CYYs^AF (()QRRJ%%&>???nnWe,G#tOjG


h
iii --
%%&;<<GH%U$vUe^E-fAdV .
  	 & 
!
!()(3 "   5  S?
 j <s   5GGF3AGF6G*G>F9?AGF;GF= !GF?A*G,G-G6G9G;G=G?GGc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   V  s      
0 
0v 
0L,E,E 
0r    c                Z  "   \        V 4      '       g   R# V P                  P                  R4      G Rj  xL
 p\        4        \        P
                  ! R 4      G Rj  xL
 pV'       d   VP                  RV 24      G Rj  xL
  R# VP                  R4      G Rj  xL
  R#  Ln LG L% L5i)zForce re-discover TV IP.Nu   🔍 Scanning...c                      \        R R7      # )T)r   )r   r   r    r   r  cmd_discover.<locals>.<lambda>\  s	    D)Ir    u   📺 Found TV at u   ❌ TV not found)r   r  r  r   r  r  rU  )r   r  r  r   s   &&  r   cmd_discoverr.  V  s     F))*<=
=C  !IJ	JB	mm/t4555mm./// >	J5/sK   2B+B#(B+B%B+*B+B'B+B)B+%B+'B+)B+c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   c  s$     N  N v N L,E,E N r    c                  a"   \        V 4      '       g   R# \        4       oV P                  P                  R4      G Rj  xL
 pV3R lp\        P
                  ! V4      G Rj  xL
 pVP                  V4      G Rj  xL
  R#  LC L! L
5i)z3Measure actual TV bandwidth + show throttle status.Nu   ⏳ Measuring speed...c            	      J  < . p  \         P                  ! . R'ORR^R7      pRVP                  9   d   \         P                  ! . R(ORR^R7      p^ RIpTP	                  RRVP                  9   d#   VP                  P                  R4      R),          MR4      pV'       d   VP                  ^4      MRpV P                  R	V 24       MV P                  R
4        S'       g#   V P                  R4       RP                  V 4      #  \         P                  ! RRRRRS.RR^
R7      p^ RIpVP	                  RVP                  4      pV'       dm   \        VP                  ^4      4      pV^8  d   V P                  RVR R24       M4V^8  d   V P                  RVR R24       MV P                  RVR R24       VP	                  RVP                  4      pV'       dD   \        VP                  ^4      4      ^ 8  d%   V P                  RVP                  ^4       R24        ^ RIpTP                  4       p	^ p
R* FZ  p \        P                  ! T^SR7      pY'       d.   \        \         P"                  ! T4      P%                  4       4      M^ ,          p
K\  	  TP                  4       T	,
          pT^ 8  dD   T
^ 8  d=   T
^,          T,          R,          pT P                  RTR R T
R!,          R R"TR# R$24       T P                  R4       T P                  R%4       T P                  R&4       RP                  T 4      #   \         d    T P                  R4        ELi ; i  \         d    T P                  R4        EL^i ; i  \         d     EKX  i ; i  \         d     Li ; i)+rA  Tr/  r;  Nzrate (\d+[KMG]?bit)z1:20r&  r  u    🔴 Throttle ACTIVE — limit: u    🟢 Throttle OFF — full speedu   ❓ Throttle status unknownu   📶 TV IP unknownrv   pingz-c3z-W2z([\d.]+)/([\d.]+)/([\d.]+)u   📶 Ping: r[   zms (excellent)z	ms (good)z	ms (slow)z(\d+)% packet lossu   ⚠️ Packet loss: %u   📶 Ping failedr   i  u   📊 API throughput: z kbps (i   zKB in z.1fs)u   📺 Netflix requirements:z*  4K = 25mbps, 1080p = 5mbps, 720p = 3mbps)rA  -srB  rC  rD  rE  )rA  classrC  rD  rE  )systemapplicationszchanneldb/tv/channelLists/all)r   r   r   r  rL  rA   r  rF  r,   rV  r;   r:   r   r   r   r  r*   r|   encode)rW  r3  rcrj  rR   r   avgloss_m_timer   total_bytesendpointelapsedkbpsr   s                 r   measurecmd_netspeed.<locals>.measurej  s+   	8K.2qJA ^^$J37dAO JJ5V\`b`i`iViryyv7Nr7Roqr%&qwwqzC?vFG?@ LL-.99U##	-c4bA.2rKA

8!((CAAGGAJ'7LL;s3i~!FG2XLL;s3iy!ABLL;s3iy!ABZZ 5qxx@F#fll1o.23FLLO3DAFG
	 JJLEKW"))(A"EA!3tzz!}';';'=#>JK X jjlU*G{{Q#a72T94T#JgkRVFVWZE[[abijmannpqr
 	R12AByyg  	8LL67	80  	-LL+,	- !   		sw   BL; 'L; L; AM BM AM  N <ANA%N ;MMM>=M>NN NN N"!N")r   r   r  r  r  r  rU  )r   r  r  rE  r   r   s   &&   @r   cmd_netspeedrG  c  sr     F		B))*BC
CCD L $$W--F
--
S DP .s4   <B	B #B	#B$B	<B=B	B	B	z/share/sandman_throttle_activec                     \         f_   ^ RIp V P                  P                  R\        4      pV P                  P                  V4      s VP                  P                  \         4       \         # )zImport sandman.py as a module.Nsandman)_sandman_modimportlib.utilutilspec_from_file_locationr   module_from_specloaderexec_module)	importlibspecs     r   _load_sandman_modulerS    sO     ~~55iP ~~66t<-r    c                    \         P                  P                  \        4      '       g   R# \        P                  R4       \        P                  ! 4         \        P                  ! \        \        4      P                  4       4      p V P                  R4      pV'       d   \        4       pVP                  V4      pVP                  4       Vn        VP#                  V4      Vn        VP#                  R4      Vn        VP$                  '       d9   VP&                  '       d'   VP)                  4        \        P                  RV4        \.        P0                  ! . R
ORR7        \         P2                  ! \        4       \        P                  R	4       R#   \*         d!   p\        P-                  RT4        Rp?LsRp?ii ; i  \*         d     Lmi ; i  \*         d     Lci ; i)zAOn startup, clean up any orphaned tc rules from a previous crash.NuA   Found orphaned throttle state — cleaning up tc rules and ARP...r   z192.168.1.254zARP restored for %sz%Orphan cleanup ARP restore failed: %sTr  zOrphaned throttle cleaned up)sysctlz-wznet.ipv4.ip_forward=0)r   r   r   THROTTLE_STATE_FILEr]   r   r   r  r*   loadsr   r   rG  rS  LinuxBandwidthThrottler_get_our_mac_our_mac_resolve_mac_tv_mac_gw_mac_restore_arpr,   warningr   r   r   )r1   r   modrB   rI   s        r   _cleanup_orphaned_throttlera    sX   77>>-..HHPQ @

4 34>>@A		'"&(C++E2A)AJu-AI7AIyyyQYYY .6@QUV
		%& HH+,  @;Q??@    sC   C	F !&F G #G F?F::F?GGG! G!c                0    V ^8  d   QhR\         R\        /# )r   r   bw)rO   r:   )r   s   "r   r   r     s     Q Q Q Qr    c                 r    \        \        4      P                  \        P                  ! R V RV/4      4       R# )r   rc  N)r   rV  r   r*   r|   )r   rc  s   &&r   _save_throttle_statere    s(    	((WeT24N)OPr    c                  `     \         P                  ! \        4       R #   \         d     R # i ; ir   )r   r   rV  r   r   r    r   r  r    s&    
		%& s    --c                     \         '       d   \         P                  '       d   \         # \        4       p \        4       pV'       g   R# V P	                  V4      s \         # )z<Import and return a LinuxBandwidthThrottler from sandman.py.N)
_throttleractiverS  r   rX  )r`  r   s     r   _get_throttlerrj    sF     zj'''

 C		B,,R0Jr    c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     R8 R8v R8L,E,E R8r    c                   aaaa"   \        V 4      '       g   R # SP                  pV'       g}   \        P                  ! . RORRR7      pRVP                  9   pV'       d&   V P
                  P                  R4      G R j  xL
  R # V P
                  P                  R4      G R j  xL
  R # V^ ,          P                  4       pVR8X  d   \        '       d1   \        P                  4       '       g   \        P                  4        R s\        P                  ! 4        \        4        V P
                  P                  R4      G R j  xL
  R # VR8X  d   R	oMGVP                  4       '       d   \        V4      oM%V P
                  P                  R
4      G R j  xL
  R # ^ o\!        V4      ^8  d0   V^,          P                  4       '       d   \        V^,          4      o\#        4       oS'       g&   V P
                  P                  R4      G R j  xL
  R # \$        P&                  P)                  R4      pV'       g   V P
                  P                  RS R24      G R j  xL
 p\        P*                  ! RR.R\        P,                  \        P,                  R7       \.        P0                  ! ^4      G R j  xL
  \$        P&                  P)                  R4      '       g   VP3                  R4      G R j  xL
  R # M'V P
                  P                  RS R24      G R j  xL
 p \.        P4                  ! VV3R l4      G R j  xL
 p\7        SS4       S^ 8  d   VP3                  RS RS R24      G R j  xL
  VV3R lp	\        '       d/   \        P                  4       '       g   \        P                  4        \.        P8                  ! V	! 4       4      sR # VP3                  RS R24      G R j  xL
  R #  ELs ELQ EL ELh EL EL ELS EL L L L L$  \:         d)   p
TP3                  RT
 24      G R j  xL 
   R p
?
R # R p
?
ii ; i5i)NTr:  r;  u9   🌐 Throttle is ON

/throttle off — restore full speedu  🌐 Throttle is OFF

/throttle 2000 — degraded (deficit 0.50+)
/throttle 1000 — visibly bad quality
/throttle 800 — very degraded (deficit 0.70+)
/throttle 500 — barely loads
/throttle 2000 60 — for 60s

Sandman auto-throttles based on deficit level.r  u)   🌐 Throttle OFF — full speed restoredr  i@  u   Usage:
/throttle 5000 — throttle to 5mbps (auto-off 120s)
/throttle 5000 60 — throttle for 60s
/throttle off — restore full speedu   ❌ TV IP unknownr<  u   ⏳ Starting MITM + zkbps throttle...r   r  r   u*   ❌ MITM failed to start — TV may be offu   ⏳ Applying c                  4   < \         P                  ! SS R 7      # ))r   bandwidth_kbps)r   throttle_apply)rc  r   s   r   r  cmd_throttle.<locals>.<lambda>8  s    Z-F-FR`b-cr    u   🌐 Throttle ON — zkbps (auto-off in r6  c                  .  <"   \         P                  ! S4      G R j  xL
  \        P                  ! 4        \	        4        \
        '       d1    S P                  P                  \
        RS R2R7      G R j  xL
  R # R #  La L
  \         d     R # i ; i5i)Nu   🌐 Throttle auto-off after u   s — full speed restoredr   r   )	r  r  r   r  r  r   botsend_messager,   )r  durations   r   auto_offcmd_throttle.<locals>.auto_off>  s     mmH---**,%' =!gg22=Qnownx  yR  PS2  T  T  T ! .
 T$ sE   BA?/B(B 6B7B ;BB BBBBz kbps (use /throttle off to stop)u   ❌ Throttle error: r@  )r   r  r   r   r   r  r  r  _throttle_timerdonecancelr   r  r  r  r:   r  r   r   r   r   r   r   r  r  rU  r  re  create_taskr,   )r   r  r  rw  is_throttledr  rx  r  r  rv  rI   rc  ru  r   s   &f         @@@r   cmd_throttler}    sk    F88D>>"HY]dhi/..++,ijjj 	 ..++  -|  }  }  }
q'--/C
e|??#7#7#9#9""$"O""$nn''(STTT d{	Xnn''  )u  v  	v  	vH
4yA~$q'//++tAw<		Bnn''(;<<< 77>>"GHLNN--0DRDHX.YZZ)%ABVZ * 2 2:;M;M	OmmAww~~CDD-- LMMM E NN--bTAQ.RSS8$$%cddR$a<--"7t;MhZWY Z[[[ ';';'='=&&(%11(*=O--"7t;[ \]]]K k } 	U 	v 	= [ 	M Td \  ^ 8mm21#67778sR  'QAQ>O1?$Q#O4$/QQ.AQO7%Q(*QO:4QAQ
O=,Q8"QP AQ0P1(QQ.P/)QP	QP <P=2P /P0P P !/P QP *P+P /Q4Q7Q:Q=Q QQQ	QP P P QP?3P64P?9Q?QQc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   U  s      )* )*f )*<+D+D )*r    c                  "   \        V 4      '       g   R# VP                  pV'       d    \        V^ ,          4      p\        R\	        RV4      4      p\        4       pW4R&   \        P                  P                  4       P                  4       VR&   \        V4       V P                  P                  RVR 24      G Rj  xL
  R# \        4       pVP                  RR4      p^ pVP                  R	4      '       d    \        P                  P                  VR	,          4      p\        P                  P                  VP                  ;'       g    \        P                   P"                  4      V,
          P%                  4       ^<,          pVP                  R
^ 4      p\)        VR4      p	\)        VR4      p
RVR R\+        V4       R\+        V4       RV	 RV
 R2pV P                  P                  V4      G Rj  xL
  R#  EL>  \         d)    T P                  P                  R4      G Rj  xL 
   R# i ; i  \&         d     Li ; i LP5i)z*Show or set the current attention deficit.Nr#   r   r"   r$   u   ✅ Deficit set to r>  zUsage: /deficit [0.0-1.0]r%   r&   g333333?g?r=  u   
📊 Session: r?  u   
🔮 Recovery: z to 0.15 | z to 0.05)r   r  r;   r!  r  r/   rw   rY   r  r6   r  r  
ValueErrorrG  rQ  rR  rS  rT  rP  r,   r`   rU   )r   r  r  valr1   ry  rz  r{  r|  recovery_15recovery_05r   s   &&          r   cmd_deficitr  U  s    F88D 	Q.Cc3sC=)CLE")$,$5$5$9$9$;$E$E$GE.!..++.A#c,KLLL LE		)S!AKyy!!	""001GHB#,,001S1Sh>O>O>S>STWYYhhjmooK 		115I$Q-K$Q-K 3  &{34J}Y?W>X Y%k+h	H 	
 ..
#
#D
)))5 M 	..++,GHHH	  		 *s   'IBG> =G;>G> 7I:AH4 ;H4 A-I4I5I;G> >)H1'H*(H1-I0H11I4I?IIIc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s      7* 7*v 7*L,E,E 7*r    c                  "   \        V 4      '       g   R# VP                  pV'       d   \        V4      ^8  d&   V P                  P	                  R4      G Rj  xL
  R# V^ ,          P                  4       p RV9   d   \        VP                  RR4      4      pM5RV9   d$   \        VP                  RR4      4      ^<,          pM\        V4      pV^<,          p \        \        T^,          P                  R4      4      w  rg\        4       pTP                  R	R
4      p	T	p
Ygr\        \        T4      4       F  p\        Y4      pT^,           p\         \"        P$                  ! ^T^,          ,           4      ,          T,          pT	T,          p	\'        T	R4      p	T^,          pT^<8  g   Kt  ^ pT^,           ^,          pK  	  TR RTR 2pRT^ ,           RT^,           RT
R RT	R RT RY,
          R 2pT P                  P	                  T4      G Rj  xL
  R#  EL  \         d)    T P                  P	                  R4      G Rj  xL 
   R# i ; i  \         d)    T P                  P	                  R4      G Rj  xL 
   R# i ; i Lu5i)zTSimulate deficit after watching N hours starting at HH:MM.
Usage: /simulate 3h 23:00NzUsage: /simulate 3h 23:00rT   r&  rR   z Bad duration. Use e.g. 3h or 90mr=   zBad time. Use e.g. 23:00r"   r#   r   rQ   u   📈 Simulation: z from z
Start deficit: r>  z
End deficit:   z (at z)
Delta:         +)r   r  r  r  r  r  r;   rO  r  r@   r:   rA   r/   rG  rangerK   BASE_IMPACT_RATEr\   r]   r  )r   r  r  dur_strr_   	total_minstart_hstart_mr1   r"   start_deficitcur_hcur_m_tmsession_min_so_farimpactend_timer   s   &&                 r   cmd_simulater    sX     F88D3t9q=nn''(CDDD 1gmmoG
'>'//#r23EG^'//#r23b8E'NEBJ	sDGMM#$67
 LEii	3'GM 53y>" .U!DHHQ1Cb1H-H$IIBN6gs#
B;EQY"$E # AeC[)H
DG9F47) 4', -!#eH: 6"237	9 	 ..
#
#D
)))a 	E  nn''(JKKK  nn''(BCCC8 *s   'J	.J	HJ	7A!H (I B#J	)A(J	JJ	)III
J	IJ	)J:I=;J J	JJ	c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     )Q )QF )Q)B)B )Qr    c           	     D  "   \        V 4      '       g   R# \        4       pVP                  RR4      p\        P                  P	                  4       pVP
                  VP                  re. p^p^ p	VP                  R4      '       d_    \        P                  P                  VR,          4      p
\        ^ WJP                  RR7      ,
          P                  4       ^<,          4      p	\        ^	4       EFH  pWVV^,          ,           ^<,          ,           ^,          pWk^,          ,           ^<,          pVR RVR 2pTp\        V^,          4       F  pV	V,           ^,           p\        WVV,           ^<,          ,           ^,          VV,           ^<,          4      p\        \        P                   ! ^V^,          ,           4      ,          V,          pVV,          p\#        VR4      pK  	  \%        W,          4      pR	V,          R
VV,
          ,          ,           pVR8  d   RMRpVP'                  V RV RVR V 24       EKK  	  V P(                  P+                  RRP-                  V4      ,           R,           \.        P0                  R7      G Rj  xL
  R#   \         d     ELi ; i L5i)z<Show text bar chart of deficit projection over next 4 hours.Nr"   r#   r%   )rR  rQ   r=   r   u   █u   ░r  u    ⚡r&  r  r>  u+   📊 Deficit projection (if watching):
```
rv   z
```r  )r   r/   rG  rw   rY   r8   r9   rQ  r!  rO  rP  r,   r  rK   r  r\   r]   r  r:   rF  r  r  rV  r   r  )r   r  r1   r"   rY   r  r  rW  	BAR_WIDTHr  r{  stept_ht_mlabelsim_deficitrR   rE   r  r  filledbarwarns   &&                     r   	cmd_curver    sV    FLEii	3'G





!C88SZZ5 EIyy!!	""001GHB!$Qzzz/F)F(U(U(WZ\(\!]
 ar	)b00B6by B&s)1SI& tby!A#a'!+B$eqyR.?&?2%EPQ	UWGWXB%R"W(==BF6!Kk3/K " [,-fnu	F(:;;$+vwauAk#%6tf=># & ..
#
#$SVZV_V_`eVf$fip$p  ~G  ~P  ~P
#  Q  Q  Q/  		. Qs8   BJ AJ "F#J JJ JJ JJ c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r     s$     E EF E)B)B Er    c                  "   \        V 4      '       g   R# VP                  pV'       gs    \        \        4      ;_uu_ 4       p\        P
                  ! V4      pRRR4       XP                  R^4      pV P                  P                  RV R24      G Rj  xL
  R#  \        V^ ,          4      pVR8  g   V^d8  d&   V P                  P                  R4      G Rj  xL
  R#  \        \        R4      ;_uu_ 4       p\        P                  ! RT/T4       RRR4       T^8X  d&   T P                  P                  R	4      G Rj  xL
  R# T P                  P                  R
T R24      G Rj  xL
  R#   + '       g   i     EL"; i  \         d    ^p EL#i ; i EL L  \         d)    T P                  P                  R4      G Rj  xL 
   R# i ; i  + '       g   i     L; i L L~5i)zHSet sandman speed multiplier. /speed 10 = 10x faster, /speed 1 = normal.Nspeedu   ⏩ Speed multiplier: zx
Usage: /speed 10 or /speed 1g?u   Speed must be 0.1–100zUsage: /speed 10r3   u   ⏩ Speed reset to 1x (normal)u   ⏩ Speed set to x)r   r  r(   
SPEED_FILEr*   r+   rG  r,   r  r  r;   r  r5   )r   r  r  r.   r   mults   &&    r   	cmd_speedr    s    F88D	j!!Qyy| "88GQ'D nn''*@Fe(fgggT!W~#:..++,EFFF $ 
j#		!		7D/1% 
 qynn''(HIIInn''*;D6(CDDD/ "!!  	D	g G nn''(:;;; 
	 	JDs   'G,F E2F 3"G,FG,>F FF  G,;G-G,G((G,+G*,G,2F	=	F FG,FG,F )GG	GG,GG,G%	 	G,*G,c                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   	  s$     )U )UV )U,*C*C )Ur    c                0  "   \        V 4      '       g   R# \        P                  ! 4       pR.pV'       g   VP                  R4       MVP	                  4        F  w  rEVP                  R^ 4      pV'       d$   \        P                  ! 4       V,
          R,          M^ pVP                  RR4      pVP                  RV R	VR
 RVR,           RVP                  RR4       R2	4       K  	  . R#Op	Rp
V	 F7  p \        V4      ;_uu_ 4       p\        P                  ! V4      p
 RRR4        M	  VP                  R4       V
f   VP                  R4       EMEV
P                  R. 4      pV
P                  R/ 4      pVP                  R4      ;'       g    Rp/ pV F?  pVP                  VP                  RR4      ^ 4      ^,           VVP                  RR4      &   KA  	  RP                  R \        VP	                  4       4       4       4      pVP                  R\        V4       RV R24       VP                  R\        V
P                  R. 4      4       R24       VP                  R\        V
P                  R. 4      4       24       VP                  R V 24       V P                  P!                  R!P                  V4      \"        P$                  R"7      G Rj  xL
  R#   + '       g   i     EK  ; i  \        \        P                  3 d     EK   i ; i L>5i)$z:Show YouTube Lounge pairing status + ad catalog freshness.Nu   📺 *Lounge & Ad Catalog*
uD   Pairing: ❌ no token cached. Run /pair while YouTube is foreground.	paired_atiQ lounge_tokenr&  `z	` paired r[   u   d ago — token `N   Nu	   …` (TV r   r  r%  u   Catalog: ❌ not found.ads_metalast_curator_runneverlangz, c              3   4   "   T F  w  rV R V 2x  K  	  R# 5i)=Nr   ).0kvs   &  r   	<genexpr>cmd_lounge.<locals>.<genexpr>,  s     L4KDAs!A3Z4Ks   z	Catalog: z ads (z	Sources: sourcesz brand channelszPending dead IDs: dead_idszLast curator run: rv   r  )zyoutube_ad_catalog.jsonzwd_ad_catalog.jsonz#/share/.ha_cache/wd_ad_catalog.json)r   r   _lounge_load_tokensrF  itemsrG  r   r(   r*   r+   r   JSONDecodeErrorrV  sortedr  r  r  r   r  )r   r  tokensrW  r  rC   r  age_dtokcatalog_pathscatrh  r.   r  metalast_runby_langru   lang_strs   &&                 r   
cmd_lounger  	  s    F++-F+,E[\!<<>KD		+q1I9BTYY[9,5E))NB/CLL1TF)E#;6GCzQZ[`[d[delmp[qZrrstu	 *<M
C	aAiil   
LL
{./ggeR www#88./::7A*1++aeeFC6H!*Lq*PGAEE&#&' 99LF7==?4KLLyS
&
!<=ySWWY%;!< =_MN)#cggj".E*F)GHI)(45
..
#
#DIIe$4ASAS
#
TTT+  "4#7#78 		$ Usg   3LB=L4K1KK1'A'LELLLK.'K1+L.K11LLLLc                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   5  s      
 
6 
(A(A 
r    c                  aa"   \        V 4      '       g   R# VP                  '       g6   V P                  P                  R\        P
                  R7      G Rj  xL
  R# RP                  VP                  4      o\        4       ;'       g    \        P                  o\        P                  ! 4       pVP                  RVV3R l4      G Rj  xL
 pV'       dH   V P                  P                  RVR,          R,           R	2\        P
                  R7      G Rj  xL
  R# V P                  P                  R
4      G Rj  xL
  R#  L Lz L/ L5i)uz   Pair YouTube with a 12-digit code from TV → YouTube → Settings →
Link with TV code. Token persists for months/years.Nue   Get a code from: TV → YouTube → Settings → Link with TV code
Then send: `/pair 123-456-789-012`r  r&  c                  6   < \         P                  ! S R SR7      # )philips)screen_namer   )r   lounge_pair_with_code)coder   s   r   r  cmd_pair.<locals>.<lambda>F  s    
009TYZr    u   ✅ Paired. Token `r  r  u   …`uN   ❌ Code rejected. Codes expire in ~5 min. Generate a fresh one and try again.)r   r  r  r  r   r  rV  r   r   r   r  get_event_looprun_in_executor)r   r  looprC   r  r   s   &&  @@r   cmd_pairr  5  s     F888nn''1 )) ( 
 	
 	

 	77388DK++:++E!!#D&&Z E nn''!%"7"<!=TB )) ( 
 	
 	

 nn''\
 	
 	
'	

	

	
sZ   %E.EE1EA EEEA EE$E=E
>EEE
Ec                D    V ^8  d   QhR\         R\        P                  /# r  r  )r   s   "r   r   r   S  s      6 6f 6<+D+D 6r    c           	     ^  "   \        V 4      '       g   R# \        4       pVP                  R/ 4      pV'       g&   V P                  P	                  R4      G Rj  xL
  R# R.p\        VP                  4       4      R
R pV Fq  pW6,          p\        V\        4      '       d   VP                  R^ 4      pV^ 8X  d   VP                  RV R24       KQ  VP                  RV R\        V4       24       Ks  	  V P                  P	                  R	P                  V4      4      G Rj  xL
  R#  L L5i)z1Show daily watch history from sandman_state.json.Nr'   u   📅 No watch history yet.u   📅 Watch history:	watch_minz  u   : 0m (rest day ✅)z: rv   i)r   r/   rG  r  r  r  keys
isinstancer   rF  rU   rV  )r   r  r1   r'   rW  sorted_datesdate_strrM   s   &&      r   cmd_historyr  S  s    FLE		+r*Inn''(DEEE"#E)..*+CD1L %gt$$kk+q1Ga<LL2hZ':;<LL2hZr-*@)ABC ! ..
#
#DIIe$4
555 	F 6s%   AD-D)C	D-"D+#D-+D-c                $    V ^8  d   QhR\         /# r   appr   )r   s   "r   r   r   o  s     :4 :4; :4r    c                J  "   \         P                  P                  \        4      '       d%   \         P                  P	                  \        4      sM^ s \        P                  ! \        4      G Rj  xL
  \        '       g   K1   \         P                  P                  \        4      '       g   K]  \         P                  P	                  \        4      pV\
        8  d   ^ sV\
        8X  d   K  \        \        R4      ;_uu_ 4       pVP                  \
        4       VP                  4       pVP                  4       sRRR4       X'       g   K  . pV F  pVP                  4       pV'       g   K   \        P                   ! V4      pTP%                  RR4      pTR8X  d   KO  TP%                  RR4      pTP%                  RR4      p	TP%                  R4      p
TR	8X  d)   T
e   R
T
R R2MRpTP'                  RT RT	 T 24       K  TR8X  g   K  TP'                  RT RT	 24       K  	  V'       g   EK  RP)                  V4      p\+        ^ \-        V4      R4       F8  pWVR,            p V P.                  P1                  \        VR7      G Rj  xL
  K:  	  EKF   EL(  + '       g   i     ELj; i  \"         d     EKn  i ; i L5  \"         d"   p\2        P5                  RT4        Rp?K  Rp?ii ; i  \"         d#   p\2        P5                  RT4        Rp?EK  Rp?ii ; i5i)z?Poll sandman_events.jsonl for new entries and push to Telegram.Nr3  rt   r&  userr   rp   r"   rI  z [r>  ]u   🤖 r  r:  u   🌟 rv   r  rr  zFailed to send log: %szLog watcher error: %s)r   r   r   rz   r  
log_offsetr  r  LOG_POLL_INTERVALr   r(   seekrK  tellrk   r*   rW  r,   rG  rF  rV  r  r  rs  rt  r]   r_  )r  sizer.   	new_linesr  r  evetyperB   rp   r"   d_strr   ichunkrI   s   &               r   log_watcherr  o  s{     
ww~~k""WW__[1


mm-...}.	477>>+..77??;/Dj 
z!k3''1z"KKM	VVX
 (  IGGIAB vr*F?FF62&"-&&+I%3:3FbQ/BE$$uQCq%@Ah&$$uQCq%9:% & 99Y'D1c$i.q4x(=''..}5.QQQ /U 	/ ('' ! & R  =KK 8!<<= 	4KK/33	4s   A.L#0J1L#L#(K3 -L#/;K3 *L#,K3 6J:K3 
L#!K3 .J/BK3 "K3 /L#25K3 ($KKKK3 L#J,	&	K3 /J?:K3 >J??K3 KK0K+%K3 +K00K3 3L >LL#L  L#c                $    V ^8  d   QhR\         /# r  r  )r   s   "r   r   r     s     D6 D6[ D6r    c                r  "    \         P                  ! ^4      G Rj  xL
  \        '       g   K-   \         P                  ! R 4      G Rj  xL
 pV'       Ed   VP	                  R4      p\
        ek   V\
        8w  d`   \        4       '       d   RMRpVR8X  d   RMRp V P                  P                  \        V R	V R
V 2R7      G Rj  xL
  \        RV 24       VsVR8X  Ed-   \         P                  ! \        4      G Rj  xL
 w  rVpV'       d   V\        8w  d   \        P	                  WU4      p\        e   \        4       '       d   RMRpV RV 2.p	V'       d   V	^ ;;,          RV R2,          uu&   V'       d3   VP	                  R4      '       d   V	P                  RVR,           24        V P                  P                  \        RP                  V	4      R7      G Rj  xL
  \        RV 24       VsV'       dp   V\         8w  de   \         e[   \        4       '       d   RMRpVR8X  d   RM
VR8X  d   RMRp V P                  P                  \        V R	V R	V 2R7      G Rj  xL
  VsV'       d   VP	                  R4      '       dx   VR,          p
V
\"        8w  dd   \"        eZ   RV
 2pVP	                  R4      '       d   VRVR,           2,          p V P                  P                  \        VR7      G Rj  xL
  V
s\         P                  ! R 4      G Rj  xL
 pV'       EdL   VP	                  R4      pVP	                  RR 4      pVe   \$        e   V\$        8w  d   V\$        ,
          p\        4       '       d   RMRpV^ 8  d   R!MR"p V P                  P                  \        T R	T R#\$         R$T RV^ 8  d   R%MR& V R2R7      G Rj  xL
  \        R'\$         R(T RV^ 8  d   R%MR& V R24       Vs\&        en   V\&        8w  dc   \        4       '       d   RMRp V P                  P                  \        T R	V'       d   R)MR* 2R7      G Rj  xL
  \        V'       d   R+MR,4       Vs\         P                  ! R- 4      G Rj  xL
 pV'       dn   VP	                  R.R&4      p\(        eO   V\(        8w  dD   \        4       '       d   RMRp V P                  P                  \        V R/V 2R7      G Rj  xL
  VsEK  EK  EK  \
        R8X  d0   Rs V P                  P                  \        R0R7      G Rj  xL
  EKL  EKO   EL5 EL
 EL  \         d     ELi ; i ELj EL  \         d     ELi ; i EL  \         d     EL&i ; i EL  \         d     ELi ; i EL EL  \         d     ELi ; i ELh  \         d     ELti ; i ELG L  \         d     Li ; i L  \         d     EK   i ; i  \         d#   p\*        P-                  R1T4        Rp?EK,  Rp?ii ; i5i)2uZ   Periodically check TV state and notify on changes — power, app, volume, mute, ambilight.Nc                      \        R 4      # )r  r  r   r    r   r  state_monitor.<locals>.<lambda>  s	    1Er    r  u   🤖u   👤r  u   📺u   💤r  u    TV → rr  u   TV → u    📱 App → r$  r%  video_idu   🎬 youtu.be/rv   u   App → PLAYINGu   ▶️PAUSEDu   ⏸u   ⏹r'  r(  r)  r*  c                      \        R 4      # r  r  r   r    r   r  r    s	    vn?Ur    r   r   Fu   🔺u   🔻z Volume u    → +r&  zVolume r9  r  r  MutedUnmutedc                      \        R 4      # )zambilight/powerr  r   r    r   r  r    s
    6BS;Tr    rY  u    💡 Ambilight → u   ❓ TV unreachablezState monitor error: %s)r  r  r   r  rG  
last_powerrn   rs  rt  r,   r~   r   last_apprH  rF  rV  last_playback
last_titlelast_volume
last_mutedlast_ambilightr]   r_  )r  rX  rY  whorZ  r_  r`  ra  rb  r   r'  msg_textr[  r\  r   diffarrowambi
ambi_staterI   s   &                   r   state_monitorr    s     mmA}}	6(()EFFBr|,)ez.A$:$<$<&&C%*d]6D!gg22$13%qhug8V 3   
 w/0"
D=292C2CDW2X,X)C6sh#,==#:#/,B,D,D&&C(+uN8*%E$FE' %ab
!,< <%&**Z*@*@ %~fZ>P=Q-R S%&)gg&:&:,9		%@P '; '" !" !"
 &
&;<#&  H$=(4,B,D,D&&C/79/D8S[_gSg%mrD%&)gg&:&:,93%qaPXz@Z '; '" !" !"
 )1 &**W"5"5 &w J.)5-25'?#)::h#7#7$,%x8H7I0J$JH!)*-''*>*>0=H +? +& %& %&
 */J &-%6%67U%VVHx&ll95 (We < ?{/F3R]K]#&#4D,B,D,D&&C.2QhFFE%&)gg&:&:,9,/5%USVRWWYaehiaiZ]oqYrswrxxy)z '; '" !" !" &}CuBdUVhs\^F_`d_eef&gh&) &1ez6I,B,D,D&&C%&)gg&:&:,9,/5%,^1\)] '; '" !" !" &gIF%*
 ")!2!23T!UUD%)XXgr%:
)5*:V,B,D,D&&C%&)gg&:&:,9,/50DZL)Q '; '" !" !" *4 m !H %!%J!gg22$18L 3    &o 	 G %  -Y!" $- % $%!" $- % $%%& (1 !)$(!)
  W!" $- % $%!" $- % $% V!" $- % $% %  	6KK1155	6sv  X7UX7X7X UX AX ,U 	U
U 5X U-AX  X  X X 33U3 &U0'U3 +X ;X ?,V +V,V 0	X :X 7X 	X $V VV X "V/#X 0A&X AV5 V2V5 AX ,$W
 W
 W W
 $1X WX "9X )W! WW! 
X 	X7X #$W4 W2W4 X7X U U*&X )U**X 0U3 3V>X VX V VX VX V V,(X +V,,X 2V5 5W X WX W
 
WX WX W! !W/,X .W//X 2W4 4X?X  X7XX X4X/(X7/X44X7c                $    V ^8  d   QhR\         /# r  r  )r   s   "r   r   r   :  s     k$ k$ k$r    c                  a"   \        4       '       g   \        P                  R4       R# / RRbRRbRRbR	R
bRRbRRbRRbRRbRRbRRbRRbRRbRRbRRbRR bR!R"bR#R$b/ R%R&bR'R(bR)R*bR+R,bR-R.bR/R0bR1R2bR3R4bR5R6bR7R8bR9R:bR;R<bR=R>bR?R@bRARBbRCRDbRERFbCRGRHRIRJRKRL/CpRpRMp \        '       g!   \        P
                  ! ^4      G Rj  xL
  K-  V'       Eg   \        4       pV'       g!   \        P
                  ! ^
4      G Rj  xL
  Kh  V RO2o \        P                  ! V3RP l4      G Rj  xL
 pRQVP                  P                  4       9   g    RRVP                  P                  4       9   d   RNp\        P                  RSS4       MO\        P                  RTVP                  P                  4       4       \        P
                  ! ^4      G Rj  xL
  EK6  V RO2oRp \        P                  ! RVRWSRXRYRZ\        P                  P                  \        P                  P                  R[7      G Rj  xL
 p  \        P                   ! VP                  P#                  4       ^<R\7      G Rj  xL
 pT'       g   EM;TP)                  R^R_7      P                  4       pR`T9  g   RaT9  d   Kt  TP+                  4       p	Rp
T	 F  pTP-                  Rb4      '       d   TP/                  RbRc4      p
 M\1        T4      ^8X  g   K@  \2        ;QJ d*    Rd TP                  4        4       F  '       d   K   RMM	  RNM! Rd TP                  4        4       4      '       g   K  TP5                  TP                  4       T4      p
K  	  T
'       g   EKO   T P6                  P9                  \        ReT
 2Rf7      G Rj  xL
  EK  T'       d8   TP&                  f*    TP;                  4        TP=                  4       G Rj  xL
  RMp\        P                  Rh4       \        P
                  ! ^
4      G Rj  xL
  EK   ELd EL, EL ELe  \         dB   p\        P                  RUT4       \        P
                  ! ^4      G Rj  xL 
   Rp?EK  Rp?ii ; i ELV EL  \        P$                   d8    TP&                  e%   \        P                  R]TP&                  4        EK   EK  i ; i EL/  \         d     EK  i ; i  \         d"   p\        P                  RgT4        Rp?ELdRp?ii ; i EL2  \         d     EL>i ; i EL  T'       dK   TP&                  f=    TP;                  4        TP=                  4       G Rj  xL 
  M  \         d     Mi ; iRMp\        P                  Rh4       \        P
                  ! ^
4      G Rj  xL 
  i ; i5i)izLMonitor TV remote key presses via ADB getevent. Requires wireless debugging.u,   ADB not available — skipping input monitorN0073zVol+0072zVol-0074Power0066r  009eBack0160z
OK/Confirm0067Up006cDown0069Left006aRight00a4r  00cfr  00d0	PlayPause00a5FastFwd00a6Rewind0071Mute0192Menu0174Exit0193Red0194Green0195Yellow0196Blue00e2zCH+00e3zCH-0090Num00087Num10088Num20089Num3008aNum4008bNum5008cNum6008dNum7008eNum8008fNum90166Info016bGuide00aeSourceFTz:5555c                  >   < \         P                  ! R RS .RR^
R7      # )r   connectTr/  )r   r   )rW   s   r   r  #adb_input_monitor.<locals>.<lambda>^  s     JNNE9f+E:>TSUWr    	connectedalreadyz(ADB connected to %s for input monitoringzADB connect failed: %szADB connect error: %sr   r7  shellgeteventz-l)r   r   )r   z%ADB getevent process exited (code %s)rO  )errorsEV_KEYDOWNKEY_r&  c              3   *   "   T F	  qR 9   x  K  	  R# 5i)0123456789abcdefNr   )r  cs   & r   r  $adb_input_monitor.<locals>.<genexpr>  s     *VIq0B+BIs   u   👤 🎮 Remote: rr  zADB getevent error: %su8   ADB input monitor disconnected — will reconnect in 10s)r   r]   r   r   r  r  r   r  r   r  rk   r,   r_  create_subprocess_execr   PIPEwait_forreadlineTimeoutError
returncodedecoderA   r"  rO  r  allrG  rs  rt  r   wait)r  	KEY_NAMESr   rT  r3  rI   r   r   r   r   	key_labelrh  rW   s   &           @r   adb_input_monitorrk  :  s    ???@(.9?.4d<BF 	  *0 ;A& 		 $Y	 17	
 	
 
 )/ 	 w )/ ;A& 	 u '-f 7=f 	  )/ 9? 	  )/ 9? 	I 
BI
}--""" y$&BmmB'''t5\F!++WX X !((.."22i188>>CS6S $IHHGPHH5qxx~~7GH!--+++ 4u6	$ 77tVWj$))..)).. D !(!1!1$++2F2F2HRT!UUD {{){4::<4'6+=

 	A||F++$%IIfb$9	1v{ss*VAGGI*Vsss*VAGGI*V'V'V$-MM!'')Q$?	  9!gg22$1:LYK8X 3    /IIK))+%% IHHOP--###g # (X , 3Q7mmB''' V++ 2!H$//Z4 %   	5KK0!44	5 &   $ /IIK))+%%  IHHOP--###s  B*W.WP+W+WP.WP7 ,P1-AP7 WAP7 P4P7 
WAS1 0R1S1 73R *R	+R /S1 7T8 9A?S1 =
S1 S1 *&S1 )S1 'S ,S-S 1S1 4W#T# .T /T# 30W#T5$W.W1P7 4P7 7R/Q>1Q42Q>7W>RWS1 	R ASS1 T8 S1 SS1 S S.)S1 -S..S1 1T<TT8 TT8  T# #T2.W1T22W8W#U:3U64U:9W:VWV3W;V><WWc                $    V ^8  d   QhR\         /# r  r  )r   s   "r   r   r     s     ,0 ,0 ,0r    c                  "   \        4       s\        P                  R\        4       V P                  P                  \        RR4      \        RR4      \        RR4      \        RR	4      \        R
R4      \        RR4      \        RR4      \        RR4      \        RR4      \        RR4      .
4      G Rj  xL
  \        '       d    \        \        R\        \        R,           R7      R7      \        R\        \        R7      R7      \        R	\        \        R,           R7      R7      ..RRR7      pV P                  P                  \        RVR7      G Rj  xL
  \         P"                  ! \$        4      G Rj  xL
  \        P                  R4       \         P"                  ! \&        4      G Rj  xL
 p\        P                  R V4       \         P(                  ! \+        V 4      4       \         P(                  ! \-        V 4      4       \         P(                  ! \/        V 4      4       R#  ELu L  \         d!   p\        P                  RT4        Rp?LRp?ii ; i L L5i)!z&Start background tasks after bot init.zBot starting. Owner chat_id: %src  zQuick overviewtvu   📺 TV RemoterI  u   🌙 Sandmanr'  u   🏠 DevicesrY  zToggle TV powerpausez
Play/PausemutezToggle muter{  zScreenshot to chatpairu!   📣 Pair YouTube (12-digit code)loungeu   📣 Ad system statusNz?v=3r  r  T)resize_keyboardis_persistentu   ⚙️ Bot restarted)r   r   r  zFailed to set keyboard: %szDiscovering TV...z	TV IP: %s)r   r   r]   r   rs  set_my_commandsr   r   r
   r   r  r  r  rt  r,   r_  r  r  ra  r   r{  r  r  rk  )r  r  rI   r   s   &   r   	post_initrv    s     LMHH.>
''
!
!8-.4)*9n-9n-7-.7L)6=)4-.6>?845#    }	9*"#3ZM\bLb=cd">:/;Z[">:*W]J];^_ 
 !%"H ''&&}CYhp&qqq
 

6
777HH !  +	+BHH["C()c*+)#./M2 r 	9KK4a88	9 8 
,sn   B1I3H(4IBH- H+H- I7I86I.I/A:I+H- -I8IIIIIc                f   \         P                  RV 4       \        '       dK   \        P                  '       d5    \        P	                  4        \        4        \         P                  R4       \        P                  ! ^ 4       R#   \         d!   p\         P                  RT4        Rp?L>Rp?ii ; i)z0Clean up throttle on SIGTERM/SIGINT before exit.u%   Received signal %d — cleaning up...zThrottle stopped gracefullyzThrottle cleanup failed: %sN)
r]   r   rh  ri  stopr  r,   r_  sysexit)signumframerI   s   && r   _graceful_shutdownr}    st    HH4f=zj'''	:OO!#HH23 HHQK  	:KK5q99	:s   3B B0B++B0c                  H   \         P                  ! 4       P                  \        4      P	                  \        4      P                  4       p V P                  \        R \        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\        4      4       V P                  \        R\         4      4       V P                  \        R	\"        4      4       V P                  \        R
\"        4      4       V P                  \        R\$        4      4       V P                  \        R\&        4      4       V P                  \        R\(        4      4       V P                  \        R\*        4      4       V P                  \        R\,        4      4       V P                  \        R\.        4      4       V P                  \        R\0        4      4       V P                  \        R\2        4      4       V P                  \        R\4        4      4       V P                  \        R\6        4      4       V P                  \        R\8        4      4       V P                  \        R\:        4      4       V P                  \        R\<        4      4       V P                  \        R\>        4      4       V P                  \        R\@        4      4       V P                  \C        \D        RR7      4       V P                  \        R\F        4      4       V P                  \        R\H        4      4       V P                  \        R\J        4      4       \L        PO                  R4       V PQ                  \R        PT                  R 7       R!# )"r   rc  r"   simulatecurver  historyrr  rq  
screenshotr{  rp  volumero  playhomerY  r1  r2  rx  r   r]   discoverr'  rn  rI  z^dev:)patternmitmnetspeedthrottlezSandman bot starting polling...)allowed_updatesN)+r   buildertoken	BOT_TOKENrv  buildadd_handlerr   r  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r.  r  r  r  r   r)  r  rG  r}  r]   r   run_pollingr   	ALL_TYPES)r  s    r   mainr    s   




%
%i
0
:
:9
E
K
K
MCOON7I67OON8Z89OON9k:;OON:|<=OON7I67OON7I67OON9k:;OON8Z89OON6845OON<@AOON489OON6845OON8Z89OON7I67OON6845OON6845OON7I67OON6845OON6845OON6845OON?4EFGOON5'23OON:|<=OON9k:;OON401OON9o>?OO('JKOON6845OON:|<=OON:|<=HH./OOF$4$4O5r    c                 
   Rp \         P                  ! 4       p \         P                  P                  V 4      '       dd   \	        \        V 4      P                  4       P                  4       4      pW!8w  d.   \         P                  ! V^	4       \        P                  RV4       \        T R4      ;_uu_ 4       pTP                  \        T4      4       RRR4       R#   \        \        3 d     LLi ; i  + '       g   i     R# ; i)zFKill any other sandman_bot processes before starting. Uses a PID file.z/share/.ha_cache/.ha_notify.pidzKilled stale bot process %dr3   N)r   r   r   r   r:   r(   rj   rk   r   r]   r   ProcessLookupErrorr  r{   rO   )pidfiler   old_pidr.   s       r   _ensure_single_instancer    s    /GYY[F77>>'""$w-,,.4467G #6@ 
gs		q	F 
	 
+ 			s#   $C A#C 5C1C.-C.1D	__main__c                &   V ^8  d   Qh/ ^ \         9   d   \        R,          ;R&   ^\         9   d   \        R,          ;R&   ^\         9   d   \        R,          ;R&   ^\         9   d   \        R,          ;R&   ^\         9   d   \        R,          ;R&   ^\         9   d   \        R,          ;R&   ^\         9   d
   \        ;R&   ^\         9   d   \        R,          ;R	&   ^\         9   d   \        R,          ;R
&   ^	\         9   d   \        R,          ;R&   # )r   Nr   r   r  r   r  r  r  r  r  r  )__conditional_annotations__r:   rO   rg   )r   s   "r   r   r      s     [ [z !  sTz  { \|  sTz } \~  C$J  \@  #* A \B !  sTz  C \D  C$J E \F  C G \H  S4Z I \Z-  D4K [- \\- " !d
 !]- \r    )r  N)F)   )r  r  r  switch.sonoff_10020b0c7c_2%switch.magic_switch_s1e_bb55_switch_1%switch.magic_switch_s1e_bb55_switch_2%switch.magic_switch_s1e_bb55_switch_3r  -switch.magic_switch_s1e_595a_kitchen_switch_2-switch.magic_switch_s1e_595a_kitchen_switch_3zswitch.tz3000_cayepv1a_ts011fzswitch.tz3000_cayepv1a_ts011f_2switch.tz3000_cayepv1a_ts011f_3z	light.fanzlight.fan_2zlight.fan_3r   )r  r  r  )r  r  r  r  )r  __doc__r  rw   r*   loggingr\   r   r  r   ry  r   logging.handlersr   pathlibr   tvmodr   telegramr   r   r   r   r   r	   r
   r   telegram.extr   r   r   r   telegram.constantsr   	_BASE_URLrO   r:   
__import___Vr  r  r  r  rJ  BOT_LOG_FILEr   r   r   rN  rM  rz   ri   rl   STATE_POLL_INTERVALr  r)   r  r  r^   r?   r/   r6   rK   rU   r`   	getLoggerr]   handlersclearsetLevelINFO	propagate	Formatter_fmt_rhsetFormatter
addHandlerStreamHandler_shrn   r~   r   r   r  r   r  r  r  r  r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r	  r  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  DEVICE_AREASr  r  r  r)  r.  rG  rh  rx  rJ  rV  rS  ra  re  r  rj  r}  r  r  r  r  r  r  r  r  rH  r  r  r  rk  rv  r}  signal_signalSIGTERMSIGINTr  r  __name__r   )r  s   @r   <module>r     s   [      	 	  
  0   W  W  W X X (4	Z$$&	'({.-
+_RD1Krd3 =	(01-$// 53    2
1
  gugvs3gugvs3gugvs3gugvs3gugvs3gugvs3 z&
" &     W\\ DNab,J     s      s " !   
     
 
  
$   ,B)(
&
&15	$1
,$~*BO	>> JNIwqTg\;0%NP / 
(1	 $  
 $$   7'2"J
0N b 
6 -@Q
R8p)*X7*t)QXED)UX
<68:4z   	 
 ! !D6Nk$\,0d
  w 2 3 w~~1 2#6L  zF r    