a
    <ij                  
   @   s0  d Z ddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlZddlZddlZddlmZmZ ejejeZejedZejeddZdZejeddd	Zejeddd
ZejedZejedZ ejedZ!ejedZ"ejedZ#ejeddZ$ejedZ%ejej&ddddddddZ'ejej&dddddddZ(ejedZ)dZ*dZ+dZ,ddda-d Z.e/ Z0e/ Z1d!d" Z2d#d$ Z3d%d& Z4d5d(d)Z5d6d*d+Z6d,d- Z7d.d/ Z8G d0d1 d1ej9j:Z;d2d3 Z<e=d4kr,e<  dS )7u   
Personal Life OS - 本地服务
- 托管 life-dashboard.html 等静态文件
- 提供 /sync (POST) 接收来自微信读书的书签按钮同步数据
- 提供 /sync.json (GET) 供工作台读取最新同步结果
    N)parse_qsurlparsez	sync.json.wereadz
apikey.txtz)https://i.weread.qq.com/api/agent/gatewaytmpzlast_reading_note.jsonzrun_reading_auto.py   读书收获zhot-videos.jsonzhot_video_auto.pyzlife-data.jsonzfavorite-collections.jsonz
.favoritesZmediazsync-favorites.js~z.cachezcodex-runtimeszcodex-primary-runtimeZdependenciesnodebinznode.exeZnode_modulesi="        tsdata<   c                  C   s$   t t jdd} t j | dS )Nr   Zhours%Y-%m-%d)datetimetimezone	timedeltanowstrftime)tz8 r   	server.py_local_today_str5   s    r   c                  C   sv   t jtsdS zLttddd} t| }W d    n1 s@0    Y  t|dpXdW S  t	yp   Y dS 0 d S )N rutf-8encodingdate)
ospathexistsLAST_NOTE_PATHopenjsonloadstrget	Exception)fr   r   r   r   _last_reading_note_date:   s    (r,   c                  C   s   t jtsdS zttddd} t| }W d    n1 s@0    Y  t|dpXd}t	|t
rp|dng }t|p|g dk rW dS |d d W S  ty   Y dS 0 d S )Nr   r   r   r   	updatedAtvideos
   )r!   r"   r#   HOT_VIDEOS_PATHr%   r&   r'   r(   r)   
isinstancedictlenr*   )r+   r   Z
updated_atr.   r   r   r   _last_hot_video_dateE   s    (r4      c              
   C   s^  t jtsdddS tjdds,dddS z ztjtj	tgt
dddd| d	}|jpXd
 }|jpfd
 }|rt| d nd|jdki}|jdkrd|d< |p|pd|j |d< ||d< |r||d< |W W t  S  tjy   ddd Y W t  S  tyB } z$dt|dW  Y d }~W t  S d }~0 0 W t  n
t  0 d S )NFzhot_video_auto.py not foundokerrorZblockingu3   热门视频抓取正在运行，请稍后再试。Tr   replacecwdcapture_outputtextr   errorstimeoutr   r7   r   z
exit code r8   stdoutstderru-   热门视频抓取超时，请稍后重试。)r!   r"   r#   HOT_VIDEO_AUTO_SCRIPT_hot_video_auto_lockacquire
subprocessrunsys
executable	WORKSPACErB   striprC   r&   loads
splitlines
returncodereleaseTimeoutExpiredr*   r(   )r@   procrB   rC   resulter   r   r   run_hot_video_auto_jobT   sF    

	$
rU   c              
   C   s  t jtsddtdS tjdds.dddS zzztjtj	tgt
dddd	| d
}|jdkrdd|j|j|jdW W t  S dd daddi}t jtrz<ttddd}t|}W d    n1 s0    Y  W n6 ty } zdd| d}W Y d }~n
d }~0 0 d|d< |j |d< |W W t  S  tjyb   ddd Y W t  S  ty } z$dt|dW  Y d }~W t  S d }~0 0 W t  n
t  0 d S )NFz'reading note generator script not found)r7   r8   Zscriptr9   u0   读书收获正在生成中，请稍后查看。r6   Tr   r:   r;   r   u   生成脚本执行失败)r7   r8   rB   rC   rO   r   r7   r   r   u0   笔记已生成，但状态文件读取失败：)r7   ZwarningrB   u!   生成超时，请稍后重试。)r!   r"   r#   READING_AUTO_SCRIPT_reading_auto_lockrF   rG   rH   rI   rJ   rK   rO   rB   rC   rP   _readtimes_cacher$   r%   r&   r'   r*   rL   rQ   r(   )r@   rR   status_datar+   rT   r   r   r   run_reading_auto_jobu   s`    
	

,&rZ   c                  C   s    dd } t j| ddd  d S )Nc                  S   s  zt t jdd} t j | }|jtkrt |dkrtdd}t	j
tdd}t	j|dd	 tt	j
|d
ddd8}|d|d dtj|dd d W d    n1 s0    Y  W n tyn } zznt	j
tdd}t	j|dd	 tt	j
|d
ddd"}|d| d W d    n1 s80    Y  W n tyX   Y n0 W Y d }~n
d }~0 0 td q d S )Nr   r   r   r5   r@   r   r   Texist_okzreading-auto.logar   r   [%Y-%m-%d %H:%M:%Sz] scheduled 08:00 result=Fensure_ascii
[scheduler-error] r   )r   r   r   r   hourREADING_AUTO_HOURr,   r   rZ   r!   r"   joinrK   makedirsr%   writer&   dumpsr*   timesleepr   r   rS   Zlog_dirr+   rT   r   r   r   loop   s$    
J6z*start_reading_auto_scheduler.<locals>.loopTzreading-auto-08targetZdaemonname	threadingZThreadstartrn   r   r   r   start_reading_auto_scheduler   s    rv   c                  C   s    dd } t j| ddd  d S )Nc                  S   s|  zt t jdd} t j | }|jtkrt |dkrtdd}t	j
td}t	j|dd tt	j
|d	d
dd8}|d|d dtj|dd d W d    n1 s0    Y  W n tyj } zzlt	j
td}t	j|dd tt	j
|d	d
dd"}|d| d W d    n1 s40    Y  W n tyT   Y n0 W Y d }~n
d }~0 0 td q d S )Nr   r   r   r5   r[   z
.workbuddyTr\   zhot-video-auto.logr^   r   r   r_   r`   z] scheduled 07:00 result=Fra   rc   rd   r   )r   r   r   r   re   HOT_VIDEO_AUTO_HOURr4   r   rU   r!   r"   rg   rK   rh   r%   ri   r&   rj   r*   rk   rl   rm   r   r   r   rn      s$    
J6z,start_hot_video_auto_scheduler.<locals>.loopTzhot-video-auto-07ro   rr   ru   r   r   r   start_hot_video_auto_scheduler   s    rx   c                       s  e Zd Z fddZdd Zdd Zdd Zdld
dZdd Zdd Z	dd Z
dd Zdd Zdd Zdd Zdd ZdmddZd d! Zd"d# Zdnd%d&Zd'd( Zd)d* Zd+d, Zd-d. Zd/d0 Zd1d2 Zd3d4 Zd5d6 Zd7d8 Zd9d: Zd;d< Zd=d> Zd?d@ Z dAdB Z!dCdD Z"dEdF Z#dGdH Z$dIdJ Z%dKdL Z&dMdN Z'dOdP Z(dQdR Z)dSdT Z*dUdV Z+dodXdYZ,dZd[ Z-d\d] Z.d^d_ Z/d`da Z0dbdc Z1ddde Z2 fdfdgZ3 fdhdiZ4djdk Z5  Z6S )pHandlerc                    s,   t  |}|dr(d|vr(| dS |S )Nztext/zcharset=z; charset=utf-8)super
guess_type
startswith)selfr"   Zcontent_type	__class__r   r   r{      s    
zHandler.guess_typec                 C   s(   |  dd |  dd |  dd d S )NzAccess-Control-Allow-Origin*zAccess-Control-Allow-MethodszGET, POST, OPTIONSzAccess-Control-Allow-HeadersContent-Type)send_headerr}   r   r   r   _cors   s    zHandler._corsc                 C   s   |  d |   |   d S )N   )send_responser   end_headersr   r   r   r   
do_OPTIONS   s    
zHandler.do_OPTIONSc                 C   sj   t jtsd S z@ttddd}|  W  d    W S 1 sD0    Y  W n tyd   Y d S 0 d S )Nr   r   r   )r!   r"   r#   KEY_PATHr%   readrL   r*   )r}   r+   r   r   r   	_read_key   s    0zHandler._read_keyr/   c           	      C   s   |   }|stdtjd|d|ddd}tjjt|d| dd	d
d}t	
 }tjj|||d$}t| dW  d    S 1 s0    Y  d S )NzWEREAD_API_KEY not configuredz1.0.4)Zskill_versionapi_nameFra   r   zBearer zapplication/json)ZAuthorizationr   ZPOST)r   headersmethod)r@   context)r   RuntimeErrorr&   rj   encodeurllibrequestRequestGW_URLsslZcreate_default_contexturlopenrM   r   decode)	r}   r   payloadr@   keybodyreqctxrespr   r   r   _gw_post   s    zHandler._gw_postc              
   C   s  | j dr|   n| j dr0|   n| j drH|   n| j dr`|   nv| j drx|   n^| j dr|   nF| j dr|   n.| j dr| 	  n| j d	r| 
  n| j d
rz<t| jdd}|r| j|nd}t|d}W nd ty } zJ| d |   |   | jtdt|dd W Y d }~d S d }~0 0 t|tr|dg ng }t|tr|ddnd|d}zFttddd"}tj ||ddd W d    n1 s 0    Y  W nd typ } zJ| d |   |   | jtdt|dd W Y d }~d S d }~0 0 | d |   | !dd |   | jtdt"|dd n| d  |   |   d S )!Nz/api/generate-reading-notez/api/delete-reading-notez/api/hot-videos/runz/api/viral-video/analyze/api/life-dataz /api/favorite-collections/importz/api/favorite-collections/loginz/api/favorite-collections/syncz!/api/favorite-collections/analyzez/syncContent-Lengthr      {}r     Fr6   booksupdatedr   )r   r   wr      rb   indent  r   r   application/json; charset=utf-8T)r7   count  )#r"   r|   _generate_reading_note_delete_reading_note_run_hot_videos_analyze_viral_video_save_life_data_import_favorite_collections_login_favorite_collections_sync_favorite_collections!_analyze_favorite_collection_itemintr   r)   rfiler   r&   rM   r   r*   r   r   r   wfileri   rj   r(   r   r1   r2   r%   	SYNC_PATHdumpr   r3   )r}   lengthr   r   rT   r   Zsnapshotr+   r   r   r   do_POST   s`    
""6
"
$
zHandler.do_POSTc                 C   s*   t dd}| j||drdndd d S Nr5   r[   r7   r   r   status)rZ   _json_responser)   )r}   rY   r   r   r   r   0  s    
zHandler._generate_reading_notec              
   C   s  z|   }t|dpd}W n@ ty^ } z(| jdd| ddd W Y d }~d S d }~0 0 | |}|s| jddddd d S d}tj|rt	| d	}t
 |k rttjtd
fD ]2}ztj|rt	| W q ty   Y q0 q| d	||d d S )Nr    r   F   请求格式错误：r6   r   r   u   日期格式错误Tzreading-note-status.json)r7   deletedr    )_read_json_bodyr(   r)   r*   r   _reading_note_file_for_dater!   r"   r#   remover,   r$   rg   rK   )r}   r   date_strrT   	note_pathr   Zstatus_pathr   r   r   r   4  s0    

zHandler._delete_reading_notec                 C   s*   t dd}| j||drdndd d S r   )rU   r   r)   )r}   rS   r   r   r   r   O  s    
zHandler._run_hot_videosc                 C   s&   t d|pdt j}|r"|dS dS )N   https?://[^\s，。)）]+r   r   )researchIgroup)r}   r>   matchr   r   r   _extract_first_urlS  s    zHandler._extract_first_urlc                 C   s   t jtsg S zbttddd}t|}W d    n1 s@0    Y  t|tr`|	dg nt|t
rn|ng W S  ty   g  Y S 0 d S )Nr   r   r   r.   )r!   r"   r#   r0   r%   r&   r'   r1   r2   r)   listr*   )r}   r+   r   r   r   r   _load_hot_videosW  s    (*zHandler._load_hot_videosc                 C   s:   t |pd  }tdd|}tdd|}|dS )Nr   z
^https?://z[?#].*$/)r(   rL   lowerr   subrstrip)r}   urlr   r   r   _normalize_video_urla  s    zHandler._normalize_video_urlc                 C   sf   |  |}|sd S |  D ]F}|  |dp8|dp8d}|r||ksX||v sX||v r|  S qd S )Nr   linkr   )r   r   r)   )r}   r   rp   videoZcurrentr   r   r   _match_hot_videog  s    

zHandler._match_hot_videohttps://www.bilibili.com/   c                 C   sd   t jj|d|dddd}t jj||d&}t| ddW  d    S 1 sV0    Y  d S )	NzoMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36z!application/json, text/plain, */*zzh-CN,zh;q=0.9,en;q=0.8)z
User-AgentZRefererZAcceptzAccept-Language)r   r[   r   ignore)r   r   r   r   r&   rM   r   r   )r}   r   Zrefererr@   r   r   r   r   r   _fetch_json_urlq  s    	zHandler._fetch_json_urlc                 C   sD  t |pd}td|}td|}|rDdtjd|di }n(|rddtjd|di }ntd| |}|	d	d
krt|	dpd|	dpi }|	dpi }|	dpi }	d }
|		dr"z8| dtjd|		di }|	dpi 	d}
W n t
y    d }
Y n0 g }z>| dtjd|	di }dd |	dp\g D }W n t
y|   g }Y n0 |	d}t|trt|
tr|
d
krt||
 dnd }d|	dpd|	drd|	d dn||||	d|	d|
|
||d d pdg|		d p&d|	d!|	d"p<dd#d$S )%Nr   z(BV[0-9A-Za-z]+)z(?:/video/|av)(\d+)z.https://api.bilibili.com/x/web-interface/view?Zbvid   Zaidu%   没有识别到 B站 BV 号或 av 号coder   messageu   B站接口未返回视频信息r   statownerZmidz)https://api.bilibili.com/x/relation/stat?ZvmidZfollowerz,https://api.bilibili.com/x/tag/archive/tags?c                 S   s    g | ]}| d r| d qS )Ztag_namer)   .0itemr   r   r   
<listcomp>      z6Handler._fetch_bilibili_video_meta.<locals>.<listcomp>Zviewr      B站titlezhttps://www.bilibili.com/video/r   Zliker   u   AI宠物带货rq   durationZdescuF   B站公开接口：标题、作者、播放、点赞、粉丝、标签)platformr   r   playsZ	playCountZlikesZ	likeCountfansZfollowerCountZplayFanRatiotagsZauthorr   ZdescriptionZanalysisBasis)r(   r   r   r   parseZ	urlencoder   r   r   r)   r*   r1   r   round)r}   r   r>   Z
bvid_matchZ	aid_matchZapir   infor   r   r   Zrelr   Ztag_datar   Zratior   r   r   _fetch_bilibili_video_meta~  sb    



4 z"Handler._fetch_bilibili_video_metac              
   C   s  z6|   }t|tr0t|dp,|dp,dnd}W n@ tyv } z(| jdd| ddd W Y d }~d S d }~0 0 | |p| }| 	|p|}| 
|}t|pi }g }|d	krzi || |}W n6 ty } z|d
|  W Y d }~n
d }~0 0 n&|dv r(|| d n
|d |  }	dd |	 D }
|
r\|d |sn||g d}t||d< d|d< | d||	|d d S )Nr   r>   r   Fr   r6   r   r   r   u   B站详情接口读取失败：)   抖音	   小红书u   视频详情、画面、音轨通常需要登录态和平台前端授权；当前本地服务拿不到你的平台登录 Cookie，所以只能使用分享文本和榜单已抓到的公开字段。u3   未识别平台，无法调用平台公开接口。c                 S   s   g | ]\}}|s|qS r   r   r   rq   r7   r   r   r   r     r   z0Handler._analyze_viral_video.<locals>.<listcomp>uo   本机缺少 yt-dlp / ffmpeg / Whisper 中的部分组件，无法下载视频、提取音频并转逐字稿。)r   r   r   ZmatchedHotVideoZmediaReadableT)r7   r   
toolStatuslimitations)r   r1   r2   r(   r)   r*   r   r   rL   _detect_video_platformr   r   append_tool_statusitemsbool)r}   r   rawrT   r   r   Zmatchedr   r   toolsmissingr   r   r   r     s8    .

(


zHandler._analyze_viral_videor   c                 C   sF   |  | |   | dd |   | jtj|ddd d S )Nr   r   Fra   r   )	r   r   r   r   r   ri   r&   rj   r   )r}   r   r   r   r   r   r     s
    
zHandler._json_responsec                 C   s:   t d|pdsd S |d d }tjtd|d| dS )Nz\d{4}-\d{2}-\d{2}r      r   u   今日读书收获_.md)r   	fullmatchr!   r"   rg   rK   )r}   r   Zyearr   r   r   r     s    z#Handler._reading_note_file_for_datec                    sl  g d  fdd}|  D ]:}| }|s8|  qt|}tdd|}tdd|}|dr|  d	|d
d   d q|dr|  d|dd   d q|dr|  d|dd   d qtd|r& sd d tdd|}d| d q|dkrB|  d q|  d| d q|  d	S )NFc                      s    r d d d S )Nz</ol>F)r   r   Zin_oloutr   r   close_ol  s    
z+Handler._markdown_to_html.<locals>.close_olz\*\*(.+?)\*\*z<strong>\1</strong>z	\*(.+?)\*z<em>\1</em># z<h1>r   z</h1>## z<h2>   z</h2>z&gt; z<blockquote>   z</blockquote>z^\d+\. z<ol>Tr   z<li>z</li>---z<hr>z<p>z</p>rc   )
rN   rL   htmlescaper   r   r|   r   r   rg   )r}   r>   r  r   lineZsafeZ	item_textr   r  r   _markdown_to_html  sB    





zHandler._markdown_to_htmlc                 C   s
  t t| jj}|dpdgd }| |}|r>tj|sz| d | 	  | 
dd |   | jdd d S t|d	dd
}| | }W d    n1 s0    Y  dt| d| d}| d | 	  | 
dd |   | j|d d S )Nr    r   r   r   r   ztext/html; charset=utf-8u   <h1>读书笔记不存在</h1>r   r   r   u   <!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>今日读书收获 u.  </title>
<style>
body{margin:0;background:#fdf2f4;color:#4a3b3e;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;line-height:1.75}
main{max-width:860px;margin:0 auto;padding:42px 22px 72px}
h1{font-size:30px;line-height:1.25;margin:0 0 18px;color:#b55d76}
h2{font-size:20px;margin:34px 0 12px;color:#b55d76}
p{margin:10px 0}
blockquote{margin:12px 0;padding:12px 16px;border-left:4px solid #d97a94;background:#fff;border-radius:8px;box-shadow:0 2px 12px rgba(217,122,148,.10)}
ol{padding-left:24px}
li{margin:8px 0}
hr{border:0;border-top:1px solid #f3d6de;margin:28px 0 14px}
a{color:#b55d76}
.back{display:inline-block;margin-bottom:20px;color:#9a8288;text-decoration:none;font-size:14px}
</style>
</head>
<body>
<main>
<a class="back" href="/life-dashboard.html">← 返回工作台</a>
z
</main>
</body>
</html>r   )r   r   r"   queryr)   r   r!   r#   r   r   r   r   r   ri   r   r%   r  r   r  r  )r}   qsr   r   r+   r   Zpager   r   r   _serve_reading_note_view	  s,    

,
z Handler._serve_reading_note_viewc                 C   sJ   t dd|}t dd|}t dd|}t dd|}t dd| S )Nz^#+\s*r   z^>\s*z	^\d+\.\s*z[*_`#]\s+ r   r   rL   )r}   r>   r   r   r   _strip_markdown8  s
    zHandler._strip_markdownc              	      s   d}|D ] zt  fddt|D }W n ty@   Y qY n0 g }||d d  D ]P}| }|drp q| |}|r|dkr|| td|dkrV qqV|rd|d d	   S q|D ]0}| | }|r|d
s|d d	   S qdS )N)u   ## 深度解读u   ## 核心观点u   ## 个人思考u   ## 金句摘录c                 3   s"   | ]\}}|   kr|V  qd S N)rL   )r   ir  Zsectionr   r   	<genexpr>C  r   z(Handler._note_summary.<locals>.<genexpr>r   r	  r  r  x      u   今日读书收获u   暂无摘要)	next	enumerateStopIterationrL   r|   r  r   r3   rg   )r}   linesZsectionsrt   partsr  strippedZcleanr   r  r   _note_summary?  s.    



zHandler._note_summaryc                    s  t j|}td|}|r&|dntdtt j	|}t
|ddd}| }W d    n1 sl0    Y  | }t fdd|D d	| d
}td|}	|	std|}	|	std|}	|	r|	dnd}
|||
 |t j|td| tdtt j	|dS )Nz(\d{4}-\d{2}-\d{2})r   r   r   r   r   c                 3   s"   | ]}| d r |V  qdS )r  N)r|   r  )r   r  r   r   r   r  _  r   z%Handler._note_meta.<locals>.<genexpr>u   读书收获（u   ）u,   (?:今日书籍|书名)：\s*《([^》]+)》u   ——\s*《([^》]+)》u   《([^》]+)》u   综合读书笔记z/reading-note-view?date=r`   r    r   Z	bookTitlesummaryr"   ZviewUrl
modifiedAt)r!   r"   basenamer   r   r   rk   r   	localtimegetmtimer%   r   rN   r  r$  relpathrK   )r}   r"   rq   r   r   r+   r>   r!  ZheadingZprimary_matchZ
book_titler   r   r   
_note_metaX  s*    (& zHandler._note_metac                 C   s   g }t jtrt tD ]\}}}|D ]}| ds<q(t j||}z|| 	| W q( t
y } z4|d|dt|t j|tddd W Y d }~q(d }~0 0 q(q|jdd dd | d|d	 d S )
Nr  r   u   读取失败r%  c                 S   s   |  dpd|  dpdfS )Nr    r   r'  r   )r   r   r   r   <lambda>  r   z.Handler._serve_reading_notes.<locals>.<lambda>T)r   reverse)r7   notes)r!   r"   r#   READING_NOTES_DIRwalkr   endswithrg   r   r,  r*   r(   r+  rK   sortr   )r}   r/  root_filesrq   r"   rT   r   r   r   _serve_reading_notesp  s*    "	zHandler._serve_reading_notesc                 C   s   t t| jj}|dpdgd }| |}|r>tj|sV| jddddd d S t	|d	d
d}|
 }W d    n1 s0    Y  | d|| || |d d S )Nr    r   r   Fu   读书笔记不存在r6   r   r   r   r   r   T)r7   r    metar  )r   r   r"   r  r)   r   r!   r#   r   r%   r   r,  r  )r}   r  r   r   r+   Zmarkdownr   r   r   _serve_reading_note  s    
&zHandler._serve_reading_notec              
   C   s  ddddg dd}t jts,| | d S zttddd}t|}W d    n1 s\0    Y  t|t	ri |d	|i}n(t|t
ri ||}ni |d
di}t|d
 |d< | | W nF ty } z,| ji |dt|ddd W Y d }~n
d }~0 0 d S )NTr   zdaily automationu   AI宠物带货赛道)r7   r-   sourceZtrackr.   r8   r   r   r   r.   r8   z'hot-videos.json format is not supportedr7   Fr6   r   r   )r!   r"   r#   r0   r   r%   r&   r'   r1   r   r2   r   r)   r*   r(   )r}   Zdefault_datar+   r   rT   r   r   r   _serve_hot_videos  s,    
(

zHandler._serve_hot_videosc              
   C   s   t jts$| ddd dd d S zfttddd}t|}W d    n1 sT0    Y  | dd|t|t	r~|
ddndd W n< ty } z$| jdt|d	d
d W Y d }~n
d }~0 0 d S )NTFr   )r7   r#   r   r-   r   r   r   __syncUpdatedAtr6   r   r   )r!   r"   r#   LIFE_DATA_PATHr   r%   r&   r'   r1   r2   r)   r*   r(   )r}   r+   r   rT   r   r   r   _serve_life_data  s    (zHandler._serve_life_datac              
   C   s  z|   }t|tr|dnd }t|tsF| jddddd W d S t|dp\t d |d< td	|d
< tt	ddd"}t
j||ddd W d    n1 s0    Y  | d|d d W n> ty } z$| jdt|ddd W Y d }~n
d }~0 0 d S )Nr   Fzdata must be an objectr6   r   r   r<    r`   Z__syncSavedAtr   r   r   r   r   T)r7   r-   r   )r   r1   r2   r)   r   r   rk   r   r%   r=  r&   r   r*   r(   )r}   r   r   r+   rT   r   r   r   r     s    
0zHandler._save_life_datac              	   C   s(   ddddgg dg ddddd	g d
S )NTr   r   r   )   知识技能   生活灵感   搞笑娱乐	   未分类)	   编程/AI   职场沟通   理财   其他知识u	   未同步u   抖音/小红书收藏需要登录态或导出的分享链接。当前本地服务已准备好入库、分类、转写和分析流程。)douyinxiaohongshur   )r7   r-   ZsourcesZ
categoriesZknowledgeSubcategories
syncStatusr.   r   r   r   r   r   _favorite_default_data  s    zHandler._favorite_default_datac              
   C   s   |   }tjts|S zttddd}t|}W d    n1 sH0    Y  t|t	s~i |di |d ddiiW S i ||}t|
dtsg |d< d|d	< |W S  t y } z2i |d
i |d dt|idW  Y d }~S d }~0 0 d S )Nr   r   r   rJ  r   u3   收藏数据格式不正确，已使用空数据。r.   Tr7   F)r7   rJ  )rK  r!   r"   r#   FAVORITE_COLLECTIONS_PATHr%   r&   r'   r1   r2   r)   r   r*   r(   )r}   defaultr+   r   rT   r   r   r   _load_favorite_data  s    (
"zHandler._load_favorite_datac                 C   sR   t d|d< ttddd"}tj||ddd W d    n1 sD0    Y  d S )	Nr`   r-   r   r   r   Fr   r   )rk   r   r%   rL  r&   r   )r}   r   r+   r   r   r   _save_favorite_data  s    zHandler._save_favorite_datac                 C   s   |  |   d S r  )r   rN  r   r   r   r   _serve_favorite_collections  s    z#Handler._serve_favorite_collectionsc                 C   s6   t | jdd}|r"| j|nd}t|dS )Nr   r   r   r   )r   r   r)   r   r   r&   rM   r   )r}   r   r   r   r   r   r     s    zHandler._read_json_bodyc                 C   sL   |pd  }d|v sd|v r dS d|v s0d|v r4dS d|v sDd	|v rHd
S dS )Nr   z
douyin.comZ	iesdouyinr   zxiaohongshu.comzxhslink.comr   zbilibili.comzb23.tvr   u   未知平台)r   )r}   r>   r   r   r   r   r     s    zHandler._detect_video_platformc                 C   sL   t d|pd}g d}|D ]"}||p(dv r||vr|| q|d d S )Nu   #([^#\s，。,.!！?？]+)r   )AI   编程   职场   沟通rF     基金   省钱   家居   穿搭   美食   搞笑   段子u   宠物r   )r   findallr   )r}   r>   r   keywordskeywordr   r   r   _extract_favorite_tags  s    zHandler._extract_favorite_tagsc                 C   sZ   t dd|pd}t dd|}t dd|}t dd|d}|sN|pLd}|d d	 S )
Nzhttps?://\S+r  r   u   #[^#\s，。,.!！?？]+z^[\d.:\sA-Za-z@/]+r  u    ，。,.!！?？   未命名收藏视频Z   r  )r}   r>   r   r   r   r   r   _clean_favorite_title  s    zHandler._clean_favorite_titlec                    s   | dd |  g d}g d}g d}t fdd|D rt fdddD r\d	S t fd
ddD rvdS t fdddD rdS dS t fdd|D rdS t fdd|D rdS dS )Nr  )rQ  rR     代码PythonCursoru   效率u   教程u   学习rS  rT     表达rF  rU     股票   存钱u   副业)rZ  r[  u   沙雕u   哈哈u   离谱u   整活u   娱乐)
u   生活rW  rX  rY  u   旅行u   收纳u   做饭u   灵感u   装修u   护肤c                 3   s   | ]}| v V  qd S r  r   r   kr>   r   r   r    r   z3Handler._classify_favorite_video.<locals>.<genexpr>c                 3   s   | ]}| v V  qd S r  r   ri  rk  r   r   r    r   )rQ  rR  rc  rd  re  u   豆包ZCoze)r@  rD  c                 3   s   | ]}| v V  qd S r  r   ri  rk  r   r   r     r   )rS  rT  rf  u   汇报u   面试)r@  rE  c                 3   s   | ]}| v V  qd S r  r   ri  rk  r   r   r  "  r   )rF  rU  rg  rh  rV  u   投资)r@  rF  )r@  rG  c                 3   s   | ]}| v V  qd S r  r   ri  rk  r   r   r  %  r   )rB  r   c                 3   s   | ]}| v V  qd S r  r   ri  rk  r   r   r  '  r   )rA  r   )rC  r   )rg   any)r}   r   r   Z	knowledgeZfunnyZlifer   rk  r   _classify_favorite_video  s"    z Handler._classify_favorite_videoc                 C   s   | dpd}| dpd}| dp(d}| dp6d}d| d	pHg }|rT|n| d
| }|dkrd|r|d| d nd }d}	n,|dkrd}d}	n|dkrd}d}	nd}d}	| |d d pd}
|
||	tddS )Nr   r`  
transcriptr   categoryrC  subcategoryu   、r   r  r@  ur   这条内容适合沉淀为一张知识卡片，重点关注“问题、方法、例子、行动”四层结构。u	   子类：u   。uW   提炼 3 个可执行步骤，并把其中最容易当天实践的一步加入待办。rA  uu   这条内容的价值在于场景启发，适合记录触发灵感的画面、物品、地点或生活方式选择。uE   拆出可复用元素：场景、预算、准备物、替代方案。rB  ux   这条内容主要依靠情绪释放和反差制造记忆点，适合分析包袱铺垫、节奏停顿和结尾反转。uQ   记录笑点公式：铺垫对象、预期方向、反差动作、结尾回扣。uZ   当前信息较少，建议先补充标题、标签或逐字稿，再做更精确归类。uT   先判断它是知识、灵感还是娱乐，再决定是否收藏进长期合集。r5   u   暂无可分析文本。r`   )r&  ZcontentInsightZactionSuggestionr-   )r)   rg   r  rk   r   )r}   r   r   rn  ro  rp  r   ZmaterialZfocusactionr&  r   r   r   _favorite_analysis+  s.    zHandler._favorite_analysisc                 C   s  z|   }t|tr"|ddnd}td| }|  }dd |dg D }g }|D ]}| }|snq\td|}|sq\|	d}	|	|v rq\| 
|}
| ||	}| ||
\}}d	tt d
  dt|dg t|  | |	||	|||
tddddi d}| ||d< d|d< || ||	 q\||dg  |d< |rndt| d|d d< | | | dt||d W n> ty } z$| jdt|ddd W Y d }~n
d }~0 0 d S )Nr>   r   z\n{2,}|\r?\n(?=.*https?://)c                 S   s    h | ]}| d r| d qS r   r   r   r   r   r   	<setcomp>L  r   z7Handler._import_favorite_collections.<locals>.<setcomp>r.   r   r   fav_r?  r5  r`   	   待转写	   待分析)idr   r   r   ro  rp  r   collectedAttranscriptStatusrn  analysisStatusanalysisr|     已生成初步分析r{  u
   已导入 u:    条收藏链接，并完成自动分类与初步分析。rJ  r   T)r7   addedr   Fr6   r   r   )r   r1   r2   r)   r   splitrL   rN  r   r   r_  rb  rm  r   rk   r3   r   r   rr  r   addrO  r   r*   r(   )r}   r   r   Zsnippetsr   existing_urlsr~  ZsnippetZ	url_matchr   r   r   ro  rp  r   rT   r   r   r   r   F  sX    

.

z$Handler._import_favorite_collectionsc                 C   s   t jtrtS tdS )Nr   )r!   r"   r#   NODE_EXEshutilwhichr   r   r   r   _node_commandu  s    zHandler._node_commandc                 C   sT   t j }g }t jtr$|t |dr<||d  |rPt j	||d< |S )NZ	NODE_PATH)
r!   environcopyr"   r#   NODE_MODULESr   r)   pathseprg   )r}   envpathsr   r   r   	_node_envz  s    


zHandler._node_envr   c           	      C   s   | dd}| dp | d|}| |}| ||\}}dtt d  d| | dph| ||||||tdd	dd
i | dpdd}| ||d< d|d< |S )Nr   r   r   ru  r?  r5  r   r`   rv  rw  r:  z
login-sync)rx  r   r   r   ro  rp  r   ry  rz  rn  r{  r|  r:  r|  r}  r{  )	r)   rb  r_  rm  r   rk   r   r   rr  )	r}   Zraw_itemindexr   r   r   ro  rp  r   r   r   r   _favorite_item_from_sync  s*    
z Handler._favorite_item_from_syncc              	   C   s   dd | dg D }g }|D ]P}| d}|r||v r:q| |t| dg t| }|| || q|r|| dg  |d< |S )Nc                 S   s    h | ]}| d r| d qS rs  r   r   r   r   r   rt    r   z1Handler._merge_favorite_videos.<locals>.<setcomp>r.   r   )r)   r  r3   r   r  )r}   r   Z
raw_videosr  r~  r   r   r   r   r   r   _merge_favorite_videos  s    
 
zHandler._merge_favorite_videosc              
   C   sJ  z|   }W n ty"   i }Y n0 t|tr:|ddnd}|  }|sb| jddddd d S tj	t
s| jddddd d S z~tjtd	d
 tjtd}tj|d	d
 tjtd}t|ddd.}|dtd d| d W d    n1 s0    Y  d}tj	|s8tdp6td}|sX| jddddd W d S g }|dv rp|d |dv r|d tj|d| ddg|ttjtjtjdkrtjnd d! |  }	d"d"d#tdd$|	d%< | |	 | d	|	|	d% d& d' W n> tyD }
 z$| jdt|
ddd W Y d }
~
n
d }
~
0 0 d S )(Nr   allFu/   未找到 Node.js，无法打开登录窗口。r6   r   r   u   sync-favorites.js 不存在。Tr\   zbrowser-profilez	login.logr^   r   r   z
[r`   z] open login chrome rc   z5C:\Program Files\Google\Chrome\Application\chrome.exechromez
chrome.exeu.   未找到 Chrome，无法打开登录窗口。)r  rH  zhttps://www.douyin.com/)r  rI  zhttps://www.xiaohongshu.com/z--user-data-dir=z--remote-debugging-port=9223z--new-windowntr   )r<   rB   rC   Zcreationflagsu   登录窗口已打开u   已打开独立 Chrome 登录窗口。请扫码登录抖音和小红书；登录完成后关闭这个登录窗口，再回到工作台点击“同步收藏”。rH  rI  r   r-   rJ  r   )r7   r   r   )r   r*   r1   r2   r)   r  r   r!   r"   r#   FAVORITE_SYNC_SCRIPTrh   FAVORITE_LOG_DIRrg   r%   ri   rk   r   r  r  r   rG   PopenrK   ZDEVNULLrq   ZCREATE_NEW_PROCESS_GROUPrN  rO  r(   )r}   r   r   r   Zprofile_dirZlog_pathZlog_filer  Zurlsr   rT   r   r   r   r     s^    
>





z#Handler._login_favorite_collectionsc           
   
   C   s8  |   }|  }|sXdddtdd|d< | | | jd||d d dd	d
 d S ztj|tddgt	ddddd| 
 d}|jpd }|rt|nd|jpdg g d}| ||dpg }|dpg }dd |D }|di dd|di dddt| dtdd|d< |drLd|d |d d< | | | t|d kt|||d! W n tjy   d"d"d#tdd|d< | | | jd||d d dd$d
 Y nf ty2 }	 zLddt|	tdd|d< | | | jd|t|	dd	d
 W Y d }	~	n
d }	~	0 0 d S )%Nu   同步失败u8   未找到 Node.js，无法启动浏览器同步脚本。r`   r  rJ  Fr   )r7   r   r8   r   r   syncr  Tr   r:   r5   )r<   r=   r>   r   r?   r@   r  r   u   同步脚本无输出)r7   r8   statusesr.   r.   r  c                 S   s"   i | ]}t |tr|d |qS )r   )r1   r2   r)   )r   sr   r   r   
<dictcomp>  r   z6Handler._sync_favorite_collections.<locals>.<dictcomp>r   u   未返回状态r   u   同步完成，新增 uf    条收藏。若新增为 0，请确认登录窗口里已经进入收藏页并能看到收藏内容。r8   u   同步脚本异常：r   )r7   r~  rS   r   u   同步超时uZ   同步超过 3 分钟未完成。请确认登录窗口没有卡在验证码或权限页。i  )r  rN  rk   r   rO  r   rG   rH   r  rK   r  rB   rL   r&   rM   rC   r  r)   r3   rQ   r*   r(   )
r}   r   r   rR   rB   rS   r~  r  Z
status_maprT   r   r   r   r     sh    



"

$

"

z"Handler._sync_favorite_collectionsc                 C   s*   t tdt tdt tddS )Nyt-dlpffmpegwhisper)ZytDlpr  Z
whisperCli)r   r  r  r   r   r   r   r     s    zHandler._tool_statusc                    sv  t jtdd tdd|dp.ttt  t j	
t d }tjddd	d
d||ddgtddddddd t j	
t d }t j	|s fddt tD }|r|d n|}t j	|stdtjd|dddddddtg
tddddddd t j	|d d  }t j	|s&td!t|d"ddd#*}|  t j	|tfW  d    S 1 sh0    Y  d S )$NTr\   z[^a-zA-Z0-9_-]r5  rx  z.%(ext)sr  z-xz--audio-formatZmp3z-or   r   r   r:   r5   )r<   r=   r>   r   r?   r@   Zcheckz.mp3c                    s(   g | ] }|  d  rtjt|qS ).)r|   r!   r"   rg   FAVORITE_MEDIA_DIR)r   rq   baser   r   r   .  r   z=Handler._transcribe_favorite_with_whisper.<locals>.<listcomp>r   u0   音频提取失败：未找到输出音频文件r  z
--languageZChinesez--modelr  z--output_formatZtxtz--output_diriX  z.txtu/   Whisper 已运行，但未找到逐字稿文件r   )r   r?   )r!   rh   r  r   r   r)   r(   r   rk   r"   rg   rG   rH   rK   r#   listdirr   splitextr%   r   rL   r+  )r}   r   Z
output_tplZ
audio_pathZmatchestranscript_pathr+   r   r  r   !_transcribe_favorite_with_whisper  sD    $

z)Handler._transcribe_favorite_with_whisperc           	   
      s  z|   }t|tr"|ddnd |  }t fdd|dg D d }|sl| jdddd	d
 W d S |  }dd | D }|r|dsd|d< d|d< nzD|dsd|d< | 	|\}}||d< ||d< d|d< d|d< W n> t
y( } z$d|d< d| d|d< W Y d }~n
d }~0 0 | ||d< d|d< ||d d< |dr\dnd|d d< | | | d ||d! W n> t
y } z$| jdt|dd"d
 W Y d }~n
d }~0 0 d S )#Nrx  r   c                 3   s    | ]}| d  kr|V  qdS )rx  Nr   )r   vZitem_idr   r   r  G  r   z<Handler._analyze_favorite_collection_item.<locals>.<genexpr>r.   Fu   收藏视频不存在r6   r   r   c                 S   s   g | ]\}}|s|qS r   r   r   r   r   r   r   L  r   z=Handler._analyze_favorite_collection_item.<locals>.<listcomp>rn  u   缺少转写组件rz  u   需要安装 yt-dlp、ffmpeg 和 Whisper 后才能从平台链接提取音频并转逐字稿。当前已先基于标题/标签完成内容分析。ZtranscriptNoteu	   转写中ZtranscriptPathu	   已转写u-   已提取音频并完成 Whisper 逐字稿。u   转写失败u'   音频提取或 Whisper 转写失败：u7   。当前已先基于标题/标签完成内容分析。r|  u	   已分析r{  r   u	   逐字稿u   标题/标签ZtranscriptBasisT)r7   r   r   r   )r   r1   r2   r)   rN  r  r   r   r   r  r*   rr  rO  r(   )	r}   r   r   r   r   r  rn  r  rT   r   r  r   r   B  s@     

&
z)Handler._analyze_favorite_collection_itemc              
      s   t | jj}|dkr |   n|dkr4|   n|dkrH|   n|dkr\|   n|dkrp|   n|dkr|   nx|dkr(| d | 	  | 
d	d
 |   tjtrttddd&}| j| d W d    n1 s0    Y  n| jtdg id n| jdrHzt }td d urj|td  tk rjtd }n| jdddidd}||da| d | 	  | 
d	d
 |   | jtjd|dddd W nr tyD } zX| d | 	  | 
d	d
 |   | jtjdt|dddd W Y d }~n
d }~0 0 n| jdr| d | 	  | 
d	d
 |   tjtrttddd&}| j| d W d    n1 s0    Y  n| jtdddd n
t   d S )Nz/api/hot-videosr   z/api/favorite-collectionsz/api/reading-notez/api/reading-notesz/reading-note-viewz
/sync.jsonr   r   r   r   r   r   r   z/api/weread-readtimesr   r   z/readdata/detailmodeZweeklyr   r[   r   T)r7   r   Fra   r6   z/api/last-reading-noteznot generated yet) r   r"   r;  r>  rP  r9  r7  r  r   r   r   r   r!   r#   r   r%   r   ri   r   r   r&   rj   r|   rk   rX   _READTIMES_TTLr   r*   r(   r$   rz   do_GET)r}   Zreq_pathr+   r   r   rT   r~   r   r   r  e  sd    

6  


&
>
8 zHandler.do_GETc                    s"   |  dd |   t   d S )NzCache-Controlzno-store)r   r   rz   r   r   r~   r   r   r     s    zHandler.end_headersc                 G   s   d S r  r   )r}   Zfmtargsr   r   r   log_message  s    zHandler.log_message)r/   )r   r   )r   )r   )7__name__
__module____qualname__r{   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r$  r,  r7  r9  r;  r>  r   rK  rN  rO  rP  r   r   r_  rb  rm  rr  r   r  r  r  r  r   r   r   r  r   r  r   r  __classcell__r   r   r~   r   ry      sf   	
3


5
*/
	/
2:$#9ry   c                  C   s  t jtsNttddd$} tjdg i| dd W d    n1 sD0    Y  t jtsttddd(} tjt	t| ddd W d    n1 s0    Y  t 
t t  t  d	tj_td
tft&}tdt  |  W d    n1 s0    Y  d S )Nr   r   r   r   Fra   r   r   Tr   z4Personal Life OS server running at http://localhost:)r!   r"   r#   r   r%   r&   r   rL  ry   rK  chdirrK   rx   rv   socketserverZThreadingTCPServerZallow_reuse_addressPORTprintZserve_forever)r+   Zhttpdr   r   r   main  s    26
r  __main__)r5   )r5   )>__doc__Zhttp.serverZhttpr  r  r&   r!   r   r   rG   rI   rk   r  r   rs   Zurllib.parser   Zurllib.requestr   r   r"   dirnameabspath__file__rK   rg   r   r   r   r$   rV   r0  r0   rD   r=  rL  r  r  
expanduserr  r  r  r  rw   rf   rX   r  ZLockrW   rE   r   r,   r4   rU   rZ   rv   rx   ZserverZSimpleHTTPRequestHandlerry   r  r  r   r   r   r   <module>   sr   " 

!
/       Z
