// ==UserScript== // @name Perplexity to Local/Notion // @namespace http://tampermonkey.net/ // @version 1.2.1 // @description Save Perplexity library/search pages to local files and Notion // @author sandleft // @match https://www.perplexity.ai/* // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect api.notion.com // @downloadURL none // ==/UserScript== !function(){"use strict";let e=!1,t=null,n=!1;const i={get token(){return(GM_getValue("notion_token","")||"").trim()},get dbId(){return(GM_getValue("db_id","")||"").trim()},get count(){const e=parseInt(GM_getValue("save_count","12"));return isNaN(e)||e<=0?12:e},get fileType(){const e=GM_getValue("file_type","md");return"md"===e||"txt"===e?e:"md"},get propTitle(){return(GM_getValue("prop_title","Title")||"Title").trim()},get propUrl(){return(GM_getValue("prop_url","URL")||"URL").trim()},get propTags(){return(GM_getValue("prop_tags","Tags")||"Tags").trim()},get propTime(){return(GM_getValue("prop_time","Time")||"Time").trim()},get autoRun(){return GM_getValue("auto_run",!1)},get autoRunDelay(){const e=parseInt(GM_getValue("auto_run_delay","5"));return isNaN(e)||e<0?5:e}};function r(e){return(e||"").replace(/&/g,"&").replace(/"/g,""")}function o(){return/^\/library(?:\/|$)/.test(window.location.pathname)}function a(){return/^\/search(?:\/|$)/.test(window.location.pathname)}function l(e){if(!e)return"";const t=String(e).trim().replace(/&/g,"&"),n=u(t);if(n)return n;const i=t.match(/https?:\/\/www\.perplexity\.ai\/search(?:\/[^\s"'<>]*)?(?:\?[^\s"'<>]*)?|(?:^|[\s"'=])((?:\/search(?:\/[^\s"'<>]*)?(?:\?[^\s"'<>]*)?))/);return i?u((i[1]||i[0]).trim()):""}function u(e){if(!e)return"";try{const t=new URL(e,window.location.origin);return t.origin!==window.location.origin?"":/^\/search(?:\/|$)/.test(t.pathname)?(t.hash="",t.href):""}catch(e){return""}}function c(e){if(!e)return"";const t=["href","data-href","data-url","data-link","to"];for(const n of t){const t=l(e.getAttribute&&e.getAttribute(n));if(t)return t}const n=e.querySelector&&e.querySelector('a[href*="/search/"], [href*="/search/"], [data-href*="/search/"], [data-url*="/search/"], [data-link*="/search/"]');if(n)for(const e of t){const t=l(n.getAttribute&&n.getAttribute(e));if(t)return t}if(e.outerHTML&&e.outerHTML.length<6e3){const t=l(e.outerHTML);if(t)return t}return""}function s(e){return(e&&e.innerText||"").replace(/\s+/g," ").trim()}function d(e){return Array.from(e.children||[]).find(e=>e.classList&&e.classList.contains("hiyori-checkbox"))}function h(){const e=document.querySelector("main")||document.body,t=Array.from(e.querySelectorAll(["a[href]",'[role="link"]','[role="button"]',"li","article",'[data-testid*="thread"]','[data-testid*="search"]','[data-testid*="library"]','[href*="/search/"]','[data-href*="/search/"]','[data-url*="/search/"]','[data-link*="/search/"]'].join(", "))),n=[],i=new Set,r=new Set;return t.forEach(t=>{const o=function(e,t=!1){const n=document.querySelector("main")||document.body;let i=e;for(;i&&i!==n&&i!==document.body;){const e=(i.tagName||"").toLowerCase(),n=i.getAttribute&&i.getAttribute("role"),r=i.getBoundingClientRect?i.getBoundingClientRect():null,o=(i.innerText||"").trim();if(("li"===e||"article"===e||"listitem"===n||"link"===n||"button"===n||r&&r.width>=260&&r.height>=24&&r.height<=180&&o.length>0)&&(t||c(i)))return i;i=i.parentElement}return e.parentElement||e}(t,!0),a=c(o)||c(t),l=s(o),u=a?`url:${a}`:`row:${l}`;(function(e,t,n){if(!e||e===document.body||e===t)return!1;if(e.closest&&e.closest("#hiyori-panel, .hiyori-float-btn"))return!1;if(n)return!0;const i=s(e),r=e.getBoundingClientRect?e.getBoundingClientRect():null,o=/(搜索|search|小时前|天前|分钟前|hour|day|minute)/i.test(i);return!(i.length<12&&!o||r&&(r.width<260||r.height<24||r.height>180))})(o,e,a)&&(i.has(u)||r.has(o)||(i.add(u),r.add(o),n.push({url:a,originalIndex:n.length,item:o,titleKey:Array.from(l).slice(0,180).join("")})))}),n}async function p(e=15e3){const t=Date.now()+e;let n=h();for(;0===n.length&&Date.now()e.url===t.url);if(n)return n}if(t.titleKey){const n=e.find(e=>e.titleKey===t.titleKey);if(n)return n}return Number.isInteger(t.libraryIndex)&&e[t.libraryIndex]?e[t.libraryIndex]:null}(t,e);return!!n&&m(n)}function b(){if(!o()||!i.autoRun||GM_getValue("hiyori_running",!1))return;if(t)return;const e=i.autoRunDelay;console.log(`[妃爱] 已开启自动抓取,${e} 秒后启动...`),t=setTimeout(()=>{t=null,o()&&!GM_getValue("hiyori_running",!1)&&(console.log("[妃爱] 自动抓取已启动!"),V(!1))},1e3*e)}function _(){t&&(clearTimeout(t),t=null)}function w(){if(document.getElementById("hiyori-panel"))return;const e=document.createElement("div");e.id="hiyori-panel",e.style.display=GM_getValue("panel_show","none"),e.innerHTML=`\n
🌸 P2L/N 🌸
\n\n \n \n\n \n \n\n
\n ▸ Notion 字段名设置(默认通常不用改)\n \n \n \n \n \n \n \n \n
\n\n \n \n\n \n \n\n \n
\n \n \n 秒后启动\n
\n\n \n
\n \n \n \n `,document.body.appendChild(e);const t=document.createElement("div");t.className="hiyori-float-btn",t.innerHTML="🌸",t.onclick=()=>{const t="none"===e.style.display?"block":"none";e.style.display=t,GM_setValue("panel_show",t)},document.body.appendChild(t),document.getElementById("h-save-config").onclick=()=>{const e=parseInt(document.getElementById("h-count").value),t=parseInt(document.getElementById("h-auto-delay").value);GM_setValue("notion_token",document.getElementById("h-token").value.trim()),GM_setValue("db_id",document.getElementById("h-dbid").value.trim()),GM_setValue("prop_title",document.getElementById("h-prop-title").value.trim()||"Title"),GM_setValue("prop_url",document.getElementById("h-prop-url").value.trim()||"URL"),GM_setValue("prop_tags",document.getElementById("h-prop-tags").value.trim()||"Tags"),GM_setValue("prop_time",document.getElementById("h-prop-time").value.trim()||"Time"),GM_setValue("save_count",String(isNaN(e)||e<=0?12:e)),GM_setValue("file_type",document.getElementById("h-filetype").value),GM_setValue("auto_run",document.getElementById("h-auto-run").checked),GM_setValue("auto_run_delay",String(isNaN(t)||t<0?5:t)),alert("宝宝!设置已经牢牢记住辣!"),_(),b()},document.getElementById("h-batch-auto").onclick=()=>V(!1),document.getElementById("h-batch-selected").onclick=()=>V(!0),document.getElementById("h-stop").onclick=()=>G(!0),GM_getValue("hiyori_running",!1)&&(document.getElementById("h-stop").style.display="block"),x(),v(),b()}function x(){if(!a())return;if(document.getElementById("hiyori-single-btn"))return;const e=document.createElement("button");e.id="hiyori-single-btn",e.className="hiyori-btn",e.innerHTML="🌸 保存当前页",e.onclick=()=>S(!0).catch(e=>console.error("[妃爱] 单篇保存出错:",e)),document.body.appendChild(e)}GM_addStyle("\n #hiyori-panel {\n position: fixed; top: 5%; right: 20px; width: 290px;\n max-height: 88vh; overflow-y: auto;\n background: #fff0f5; border: 2px solid #ffb6c1;\n border-radius: 15px; z-index: 10000; padding: 15px;\n font-family: 'Microsoft YaHei', sans-serif;\n box-shadow: 0 4px 15px rgba(255,182,193,0.4);\n scrollbar-width: thin; scrollbar-color: #ffb6c1 #fff0f5;\n }\n .hiyori-title { color: #ff69b4; font-weight: bold; text-align: center; margin-bottom: 10px; font-size: 16px; }\n .hiyori-label { font-size: 12px; color: #ff69b4; font-weight: bold; margin-top: 5px; display: block; }\n .hiyori-input, .hiyori-select {\n width: 100%; padding: 6px; margin: 3px 0 8px 0;\n border: 1px solid #ffb6c1; border-radius: 5px;\n font-size: 12px; box-sizing: border-box;\n }\n .hiyori-btn {\n width: 100%; padding: 8px; margin-top: 5px;\n background: #ffb6c1; color: white; border: none;\n border-radius: 20px; cursor: pointer; font-weight: bold; transition: 0.3s;\n }\n .hiyori-btn:hover { background: #ff69b4; transform: scale(1.02); }\n .hiyori-float-btn {\n position: fixed; bottom: 30px; right: 30px;\n width: 60px; height: 60px; background: #ffb6c1; color: white;\n border-radius: 50%; border: 4px solid #fff; cursor: pointer;\n z-index: 9999; font-size: 30px;\n display: flex; align-items: center; justify-content: center;\n box-shadow: 0 4px 10px rgba(0,0,0,0.2);\n }\n .hiyori-checkbox { margin-right: 10px; transform: scale(1.3); cursor: pointer; accent-color: #ff69b4; }\n .hiyori-library-item {\n position: relative !important;\n padding-left: 30px !important;\n }\n .hiyori-library-item > .hiyori-checkbox {\n position: absolute; left: 6px; top: 50%;\n transform: translateY(-50%) scale(1.15);\n z-index: 10001; margin: 0;\n }\n #hiyori-single-btn {\n position: fixed; bottom: 100px; right: 30px; width: 120px;\n z-index: 9998; box-shadow: 0 4px 6px rgba(0,0,0,0.1);\n }\n #hiyori-panel details > summary {\n color: #ff69b4; font-size: 12px; cursor: pointer; font-weight: bold;\n }\n ");let M=location.href;function v(){o()&&h().forEach(e=>{const t=e.item;if(!t||d(t))return;const n=document.createElement("input");n.type="checkbox",n.className="hiyori-checkbox",n.dataset.hiyoriUrl=e.url||"",n.dataset.hiyoriIndex=String(e.originalIndex),n.dataset.hiyoriTitleKey=e.titleKey||"",["pointerdown","mousedown","mouseup"].forEach(e=>{n.addEventListener(e,e=>e.stopPropagation())}),n.addEventListener("click",e=>{const t=n.checked;e.preventDefault(),e.stopPropagation(),setTimeout(()=>{n.checked=t},0)}),t.classList.add("hiyori-library-item"),t.insertBefore(n,t.firstChild)})}function G(e=!1){if(GM_setValue("hiyori_running",!1),GM_setValue("hiyori_queue",[]),e){const e=GM_getValue("hiyori_failures",[]);Array.isArray(e)&&e.length>0?alert(`🛑 已停止!以下文章处理失败或写入 Notion 失败:\n${e.join("\n")}`):alert("🛑 已经乖乖停下啦!")}GM_setValue("hiyori_failures",[]);const t=document.getElementById("h-stop");t&&(t.style.display="none")}async function V(e){if(!o())return void alert("宝宝,请先打开 Perplexity 的 Library/历史页面再批量抓取哦!");if(GM_getValue("hiyori_running",!1)&&!confirm("检测到已有批量任务运行中,是否强制覆盖?"))return;const t=await p();v();const n=[],r=new Set;if(e?h().forEach(e=>{const t=d(e.item),i=e.url||e.titleKey||String(e.originalIndex);t&&t.checked&&!r.has(i)&&(r.add(i),n.push(y(e)))}):t.slice(0,i.count).forEach(e=>{const t=e.url||e.titleKey||String(e.originalIndex);r.has(t)||(r.add(t),n.push(y(e)))}),0===n.length)return void alert("宝宝,没有找到文章呀,请确认 Library/历史页面已经加载完毕,或者已勾选需要保存的文章哦!");n.reverse(),GM_setValue("hiyori_queue",n),GM_setValue("hiyori_running",!0),GM_setValue("hiyori_failures",[]);const a=document.getElementById("h-stop");a&&(a.style.display="block"),k()}async function k(){if(!GM_getValue("hiyori_running",!1))return;if(n)return;const e=GM_getValue("hiyori_queue",[]);if(!Array.isArray(e))return console.error("[妃爱] 队列数据损坏,已静默重置"),G(!1),void alert("⚠️ 队列数据异常,已自动重置,请重新开始。");if(e.length>0){const t=e.shift();if(GM_setValue("hiyori_current_tag_index",t.originalIndex),GM_setValue("hiyori_queue",e),Number.isInteger(t.libraryIndex)){if(!o())return e.unshift(t),GM_setValue("hiyori_queue",e),void(window.location.href="https://www.perplexity.ai/library");n=!0;const i=await g(t);return n=!1,void(i||(console.error("[妃爱] 无法通过模拟点按打开 Library 条目:",t),E(t.titleKey||`Library 第 ${t.originalIndex+1} 条`),setTimeout(()=>k(),700)))}t.url&&t.url===window.location.href?window.location.reload():t.url?window.location.href=t.url:(console.error("[妃爱] 队列条目缺少可打开的信息,已跳过:",t),E(t.titleKey||`Library 第 ${t.originalIndex+1} 条`),setTimeout(()=>k(),700))}else{GM_setValue("hiyori_running",!1);const e=GM_getValue("hiyori_failures",[]);GM_setValue("hiyori_failures",[]),Array.isArray(e)&&e.length>0?alert(`🌸 批量完成!但以下文章处理失败或写入 Notion 失败:\n${e.join("\n")}`):alert("🌸 宝宝!所有的素材都已经完美归档啦!"),window.location.href="https://www.perplexity.ai/library"}}function T(e){return new Promise(t=>setTimeout(t,e))}function I(e,t){return Array.from(e).slice(0,t).join("")}function E(e){const t=GM_getValue("hiyori_failures",[]);Array.isArray(t)&&(t.push(e),GM_setValue("hiyori_failures",t))}async function S(t=!1){if(e)console.warn("[妃爱] 已在处理中,跳过重复调用");else{e=!0;try{const e=await async function(e=15e3,t=1500){const n=Date.now()+e;let i=-1,r=0;for(;Date.now()200)if(n===i){if(0===r&&(r=Date.now()),Date.now()-r>=t)return e}else r=0,i=n;await T(400)}return document.querySelector("main")||document.body}();if(!t&&!GM_getValue("hiyori_running",!1))return;const n=await async function(e=8e3){const t=new Set(["","Perplexity","Perplexity AI","New Thread","Ask anything","Untitled","新建对话","新线程","新しいスレッド","新しい質問","새 스레드"]),n=Date.now()+e;for(;Date.now(){e.href&&!e.href.includes("perplexity.ai")&&o.add(e.href)});const a=Array.from(e.querySelectorAll("img")).filter(e=>(e.currentSrc||e.dataset.src||e.src||"").startsWith("http")&&(e.naturalWidth>50||!!e.dataset.src)),l=(e.innerText||"").trim();if(!l)return console.warn("[妃爱] 页面内容为空,跳过:",n),t&&alert("宝宝,页面内容还没加载出来哦,请稍后再试!"),void(t||k());const u=o.size>0?"\n\n---\n### 🌸 引用来源 (Sources)\n"+Array.from(o).map((e,t)=>`[${t+1}] ${e}`).join("\n"):"",c=l+u+(a.length>0?"\n\n---\n### 🌸 提取的附图资源\n"+a.map(e=>{const t=e.currentSrc||e.dataset.src||e.src;return"md"===i.fileType?`![图片](<${t}>)`:`[图片链接]: ${t}`}).join("\n\n"):"");let s="综合";if(!t){const e=GM_getValue("hiyori_current_tag_index",0);s=e<4?"经济":e<10?"政治军事":"综合"}!function(e,t,n){const i=I(e.replace(/[/\\:*?"<>|]/g,"_"),40),r="md"===n?"text/markdown":"text/plain",o=URL.createObjectURL(new Blob([t],{type:r+";charset=utf-8"})),a=document.createElement("a");a.href=o,a.download=`${i}.${n}`,document.body.appendChild(a),a.click(),document.body.removeChild(a),setTimeout(()=>URL.revokeObjectURL(o),1e4)}(n,c,i.fileType),i.token&&i.dbId?await B(n,r,c,s,t):t?alert("宝宝,只帮您保存了本地文件哦!Notion 配置未填写。"):k()}catch(e){console.error("[妃爱] processCurrentPage 出错:",e),!t&&GM_getValue("hiyori_running",!1)&&k()}finally{e=!1}}}function B(e,t,n,r,o,a=0){return new Promise(l=>{const u=Array.from(n).length>188100,c=function(e,t){const n=Array.from(e),i=[];for(let e=0;e({object:"block",type:"paragraph",paragraph:{rich_text:[{type:"text",text:{content:e}}]}})),s=new Date,d=[s.getFullYear(),String(s.getMonth()+1).padStart(2,"0"),String(s.getDate()).padStart(2,"0")].join("-"),h={};h[i.propTitle]={title:[{type:"text",text:{content:I(e,100)}}]},h[i.propUrl]={url:t},h[i.propTags]={multi_select:[{name:r}]},h[i.propTime]={date:{start:d}},GM_xmlhttpRequest({method:"POST",url:"https://api.notion.com/v1/pages",timeout:2e4,headers:{Authorization:`Bearer ${i.token}`,"Content-Type":"application/json","Notion-Version":"2022-06-28"},data:JSON.stringify({parent:{database_id:i.dbId},properties:h,children:c}),onload(i){if(429===i.status&&a<3){const i=8e3*(a+1);return console.warn(`[妃爱] Notion 限流,${i/1e3}s 后重试(第 ${a+1} 次)`),void setTimeout(()=>B(e,t,n,r,o,a+1).then(l),i)}i.status<200||i.status>=300?(console.error("[妃爱] Notion 写入失败:",i.status,i.responseText),E(e)):u&&console.warn("[妃爱] 内容超限,已截断至约 18.6 万字符:",e),l(),!o&&GM_getValue("hiyori_running",!1)&&k()},onerror(t){console.error("[妃爱] Notion 网络错误:",t),E(e),l(),!o&&GM_getValue("hiyori_running",!1)&&k()},ontimeout(){console.error("[妃爱] Notion 请求超时,已跳过:",e),E(e),l(),!o&&GM_getValue("hiyori_running",!1)&&k()}})})}new MutationObserver(()=>{if(location.href===M)return;M=location.href,n=!1;const e=document.getElementById("hiyori-single-btn");e&&e.remove(),x(),o()?(v(),b(),GM_getValue("hiyori_running",!1)&&setTimeout(()=>k(),700)):_(),a()&&GM_getValue("hiyori_running",!1)&&S().catch(e=>{console.error("[妃爱] 自动处理入口出错:",e),GM_getValue("hiyori_running",!1)&&k()})}).observe(document.body,{childList:!0,subtree:!0}),"complete"===document.readyState?w():window.addEventListener("load",w),setInterval(v,2e3),o()&&(b(),GM_getValue("hiyori_running",!1)&&setTimeout(()=>k(),700)),a()&&GM_getValue("hiyori_running",!1)&&S().catch(e=>{console.error("[妃爱] 自动处理入口出错:",e),GM_getValue("hiyori_running",!1)&&k()})}();