Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94d8fa455d | |||
| 9f959ca87b | |||
| 7f4ab50557 | |||
| e4e3884ffc | |||
| a609379d20 | |||
| d78412646e |
@@ -1 +1 @@
|
||||
0.3.34
|
||||
0.3.51
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import{x as qu,m as $_,h as Ce,B as Hg,d as q_,o as K_,a as Tt,c as Nt,K as ml,L as _l,b as Q,F as Vr,r as Gr,f as $t,w as qt,e as Oe,v as Ka,j as Hr,i as Q_,t as wt,n as Nc,y as Ei,l as zc,p as J_,E as j_,u as t1}from"./index-hm1O9sxT.js";import{a as e1,h as r1,c as i1,i as n1,j as a1,t as o1,_ as s1}from"./_plugin-vue_export-helper-D4DENoBS.js";import l1 from"./CloudConfig-Dl6za0gm.js";import u1 from"./SystemConfig-CwYIM6EH.js";import f1 from"./SaveRecords-DQrV5BWq.js";import"./index-Bn7NwETH.js";import"./CloudBadge-CNjyd2IF.js";/*! *****************************************************************************
|
||||
import{x as qu,m as $_,h as Ce,B as Hg,d as q_,o as K_,a as Tt,c as Nt,K as ml,L as _l,b as Q,F as Vr,r as Gr,f as $t,w as qt,e as Oe,v as Ka,j as Hr,i as Q_,t as wt,n as Nc,y as Ei,l as zc,p as J_,E as j_,u as t1}from"./index-CK-9TfWb.js";import{a as e1,h as r1,c as i1,i as n1,j as a1,t as o1,_ as s1}from"./_plugin-vue_export-helper-D4DENoBS.js";import l1 from"./CloudConfig-vPJUzF1U.js";import u1 from"./SystemConfig-tnevz2yA.js";import f1 from"./SaveRecords-BcXS42JB.js";import"./index-Bn7NwETH.js";import"./CloudBadge-OCEQ2GP-.js";/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
@@ -1 +1 @@
|
||||
import{d as B,o as N,a as V,c as I,b as n,t as c,f as e,w as t,h as g,v as y,j as u,k as r,C as M,l,D as T,G as j,H as q,I as z,J as A,u as D,z as H}from"./index-hm1O9sxT.js";import{a as L,_ as R}from"./_plugin-vue_export-helper-D4DENoBS.js";const E={class:"admin-layout"},G={class:"admin-sidebar"},J={class:"sidebar-brand"},W={class:"sidebar-brand-text"},F={class:"sidebar-version"},K={class:"admin-content"},O={class:"content-header"},P={class:"content-breadcrumb"},Q={class:"breadcrumb-current"},U={class:"content-actions"},X={class:"content-body"},Y=B({__name:"AdminLayout",setup(Z){const d=D(),f=H(),m=g(""),_=g(""),b={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},p=y(()=>{const o=f.name;return o==="admin-cloud-configs"?"cloud-configs-toggle":o==="admin-cleanup"?"cloud-configs-cleanup":o==="admin-system"?f.query.section||"sys-site":o==="admin-save-records"?"save-records":"dashboard"}),x=y(()=>b[p.value]||"仪表盘");function w(o){o==="dashboard"?d.push("/admin/dashboard"):o==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):o==="cloud-configs-cleanup"?d.push("/admin/cleanup"):o.startsWith("sys-")?d.push({path:"/admin/system",query:{section:o}}):o==="save-records"?d.push("/admin/save-records"):o==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function h(){d.push("/")}return N(async()=>{try{const o=await L();m.value=o.site_name||""}catch{}try{const s=await(await fetch("/health")).json();_.value=s.version}catch{}}),(o,s)=>{const i=u("el-icon"),a=u("el-menu-item"),v=u("el-sub-menu"),C=u("el-menu"),k=u("el-button"),S=u("router-view");return V(),I("div",E,[n("aside",G,[n("div",J,[s[1]||(s[1]=n("div",{class:"sidebar-logo"},"☁️",-1)),n("div",W,[n("h2",null,c(m.value||"CloudSearch"),1),s[0]||(s[0]=n("p",null,"管理控制台",-1))])]),e(C,{"default-active":p.value,class:"sidebar-menu",onSelect:w},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=n("span",null,"仪表盘",-1))]),_:1}),e(v,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=n("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(v,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=n("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(q))]),_:1}),s[13]||(s[13]=n("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=n("div",{class:"sidebar-spacer"},null,-1)),n("div",F,"v"+c(_.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(z))]),_:1}),s[14]||(s[14]=n("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),n("div",K,[n("header",O,[n("div",P,[n("span",Q,c(x.value),1)]),n("div",U,[e(k,{text:"",size:"small",onClick:h},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),n("main",X,[e(S)])])])}}}),es=R(Y,[["__scopeId","data-v-647abf08"]]);export{es as default};
|
||||
import{d as B,o as N,a as V,c as I,b as n,t as c,f as e,w as t,h as g,v as y,j as u,k as r,C as M,l,D as T,G as j,H as q,I as z,J as A,u as D,z as H}from"./index-CK-9TfWb.js";import{a as L,_ as R}from"./_plugin-vue_export-helper-D4DENoBS.js";const E={class:"admin-layout"},G={class:"admin-sidebar"},J={class:"sidebar-brand"},W={class:"sidebar-brand-text"},F={class:"sidebar-version"},K={class:"admin-content"},O={class:"content-header"},P={class:"content-breadcrumb"},Q={class:"breadcrumb-current"},U={class:"content-actions"},X={class:"content-body"},Y=B({__name:"AdminLayout",setup(Z){const d=D(),f=H(),m=g(""),_=g(""),b={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},p=y(()=>{const o=f.name;return o==="admin-cloud-configs"?"cloud-configs-toggle":o==="admin-cleanup"?"cloud-configs-cleanup":o==="admin-system"?f.query.section||"sys-site":o==="admin-save-records"?"save-records":"dashboard"}),x=y(()=>b[p.value]||"仪表盘");function w(o){o==="dashboard"?d.push("/admin/dashboard"):o==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):o==="cloud-configs-cleanup"?d.push("/admin/cleanup"):o.startsWith("sys-")?d.push({path:"/admin/system",query:{section:o}}):o==="save-records"?d.push("/admin/save-records"):o==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function h(){d.push("/")}return N(async()=>{try{const o=await L();m.value=o.site_name||""}catch{}try{const s=await(await fetch("/health")).json();_.value=s.version}catch{}}),(o,s)=>{const i=u("el-icon"),a=u("el-menu-item"),v=u("el-sub-menu"),C=u("el-menu"),k=u("el-button"),S=u("router-view");return V(),I("div",E,[n("aside",G,[n("div",J,[s[1]||(s[1]=n("div",{class:"sidebar-logo"},"☁️",-1)),n("div",W,[n("h2",null,c(m.value||"CloudSearch"),1),s[0]||(s[0]=n("p",null,"管理控制台",-1))])]),e(C,{"default-active":p.value,class:"sidebar-menu",onSelect:w},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=n("span",null,"仪表盘",-1))]),_:1}),e(v,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=n("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(v,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=n("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(q))]),_:1}),s[13]||(s[13]=n("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=n("div",{class:"sidebar-spacer"},null,-1)),n("div",F,"v"+c(_.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(z))]),_:1}),s[14]||(s[14]=n("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),n("div",K,[n("header",O,[n("div",P,[n("span",Q,c(x.value),1)]),n("div",U,[e(k,{text:"",size:"small",onClick:h},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),n("main",X,[e(S)])])])}}}),es=R(Y,[["__scopeId","data-v-647abf08"]]);export{es as default};
|
||||
@@ -1 +1 @@
|
||||
import{d as k,o as C,a as w,c as y,b as a,t as m,f as t,w as i,g as x,e as L,h as d,j as p,l as N,i as S,E as B}from"./index-hm1O9sxT.js";import{a as E,d as M,_ as U}from"./_plugin-vue_export-helper-D4DENoBS.js";const j={class:"admin-login-page"},q={class:"login-card"},A={class:"login-brand"},I={class:"login-title"},K={key:0,class:"error-msg"},R={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const f=d(),u=d(!1),c=d(""),g=d(""),v=d("");E().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=f.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await M(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return w(),y("div",j,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",q,[a("div",A,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",I,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:f,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=_=>s.username=_),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=_=>s.password=_),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(w(),y("p",K,m(c.value),1)):L("",!0),a("p",R,"CloudSearch v"+m(v.value),1)])])}}}),G=U(z,[["__scopeId","data-v-bd0b6672"]]);export{G as default};
|
||||
import{d as k,o as C,a as w,c as y,b as a,t as m,f as t,w as i,g as x,e as L,h as d,j as p,l as N,i as S,E as B}from"./index-CK-9TfWb.js";import{a as E,d as M,_ as U}from"./_plugin-vue_export-helper-D4DENoBS.js";const j={class:"admin-login-page"},q={class:"login-card"},A={class:"login-brand"},I={class:"login-title"},K={key:0,class:"error-msg"},R={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const f=d(),u=d(!1),c=d(""),g=d(""),v=d("");E().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=f.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await M(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return w(),y("div",j,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",q,[a("div",A,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",I,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:f,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=_=>s.username=_),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=_=>s.password=_),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(w(),y("p",K,m(c.value),1)):L("",!0),a("p",R,"CloudSearch v"+m(v.value),1)])])}}}),G=U(z,[["__scopeId","data-v-bd0b6672"]]);export{G as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{C as s,b as a,a as n}from"./index-Bn7NwETH.js";import{d as l,a as t,c,p as d,k as o,e as r,l as u,t as m}from"./index-hm1O9sxT.js";import{_}from"./_plugin-vue_export-helper-D4DENoBS.js";const p=["src"],i=l({__name:"CloudBadge",props:{cloud_type:{},showIcon:{type:Boolean}},setup(e){return(C,y)=>(t(),c("span",{class:"cloud-badge",style:d({background:o(s)[e.cloud_type]})},[e.showIcon&&o(a)[e.cloud_type]?(t(),c("img",{key:0,src:o(a)[e.cloud_type],class:"badge-icon"},null,8,p)):r("",!0),u(" "+m(o(n)[e.cloud_type]),1)],4))}}),L=_(i,[["__scopeId","data-v-9106805f"]]);export{L as C};
|
||||
import{C as s,b as a,a as n}from"./index-Bn7NwETH.js";import{d as l,a as t,c,p as d,k as o,e as r,l as u,t as m}from"./index-CK-9TfWb.js";import{_}from"./_plugin-vue_export-helper-D4DENoBS.js";const p=["src"],i=l({__name:"CloudBadge",props:{cloud_type:{},showIcon:{type:Boolean}},setup(e){return(C,y)=>(t(),c("span",{class:"cloud-badge",style:d({background:o(s)[e.cloud_type]})},[e.showIcon&&o(a)[e.cloud_type]?(t(),c("img",{key:0,src:o(a)[e.cloud_type],class:"badge-icon"},null,8,p)):r("",!0),u(" "+m(o(n)[e.cloud_type]),1)],4))}}),L=_(i,[["__scopeId","data-v-9106805f"]]);export{L as C};
|
||||
@@ -1,4 +1,4 @@
|
||||
import{d as ke,o as L,m as ve,E as _,a as c,c as k,f as n,w as a,b as r,h as C,j as p,i as be,F as R,r as K,t as v,y as g,l as d,e as A,k as Ce,M as he,p as xe,n as H,K as Be,L as Te,v as h}from"./index-hm1O9sxT.js";import{a as x}from"./index-Bn7NwETH.js";import{c as we,k as Fe,h as Ne,l as G,t as Ve,u as P,m as ze,n as Se,o as $e,_ as Ue}from"./_plugin-vue_export-helper-D4DENoBS.js";import{C as De}from"./CloudBadge-CNjyd2IF.js";const Ie={class:"cloud-config"},Me={class:"cloud-toggle-grid"},Oe=["src"],qe={class:"cloud-label"},Ee={class:"toolbar"},Le={key:0,class:"nickname-text"},Re={key:0,class:"promotion-text"},Ke={key:0,class:"uid-cell"},Ae={key:0,class:"verifying"},He={key:0,class:"storage-cell"},Ge={class:"storage-bar-wrap"},Pe={class:"storage-text"},je={class:"storage-used"},Je={class:"storage-total"},Qe={class:"storage-free"},We={key:0,class:"save-count"},Xe={style:{"line-height":"1.6"}},Ye={class:"cookie-tips-header"},Ze={class:"cookie-tips-title"},et=["innerHTML"],tt=ke({__name:"CloudConfig",setup(ot){const z=C([]),D=C(),F=C([]),B=C(!1),T=C(!1),b=C(null),l=be({cloud_type:"",nickname:"",promotion_account:"",is_transfer_enabled:!1,cookie:"",_verifying:!1,_storageUsed:"",_storageTotal:""}),j=h(()=>({cloud_type:[{required:!0,message:"请选择网盘类型",trigger:"change"}],nickname:[{required:!1,message:"请填写昵称(区分多个同类型网盘)",trigger:"blur"}],promotion_account:[{required:!0,message:"请填写推广平台及账号",trigger:"blur"}]})),J=h(()=>Object.entries(x)),Q=h(()=>{if(!l.cloud_type)return"请先选择网盘类型";const t=l.cloud_type;return t==="quark"||t==="baidu"?`请输入 ${x[t]||t} 的完整 Cookie`:b.value?"留空则保持原有":"输入完整 Cookie"}),W=h(()=>x[l.cloud_type]||l.cloud_type||""),X=h(()=>{const t=l.cloud_type;return t?{quark:`<li>在电脑上打开 <a href="https://pan.quark.cn" target="_blank">pan.quark.cn</a> 并登录你的夸克账号</li>
|
||||
import{d as ke,o as L,m as ve,E as _,a as c,c as k,f as n,w as a,b as r,h as C,j as p,i as be,F as R,r as K,t as v,y as g,l as d,e as A,k as Ce,M as he,p as xe,n as H,K as Be,L as Te,v as h}from"./index-CK-9TfWb.js";import{a as x}from"./index-Bn7NwETH.js";import{c as we,k as Fe,h as Ne,l as G,t as Ve,u as P,m as ze,n as Se,o as $e,_ as Ue}from"./_plugin-vue_export-helper-D4DENoBS.js";import{C as De}from"./CloudBadge-OCEQ2GP-.js";const Ie={class:"cloud-config"},Me={class:"cloud-toggle-grid"},Oe=["src"],qe={class:"cloud-label"},Ee={class:"toolbar"},Le={key:0,class:"nickname-text"},Re={key:0,class:"promotion-text"},Ke={key:0,class:"uid-cell"},Ae={key:0,class:"verifying"},He={key:0,class:"storage-cell"},Ge={class:"storage-bar-wrap"},Pe={class:"storage-text"},je={class:"storage-used"},Je={class:"storage-total"},Qe={class:"storage-free"},We={key:0,class:"save-count"},Xe={style:{"line-height":"1.6"}},Ye={class:"cookie-tips-header"},Ze={class:"cookie-tips-title"},et=["innerHTML"],tt=ke({__name:"CloudConfig",setup(ot){const z=C([]),D=C(),F=C([]),B=C(!1),T=C(!1),b=C(null),l=be({cloud_type:"",nickname:"",promotion_account:"",is_transfer_enabled:!1,cookie:"",_verifying:!1,_storageUsed:"",_storageTotal:""}),j=h(()=>({cloud_type:[{required:!0,message:"请选择网盘类型",trigger:"change"}],nickname:[{required:!1,message:"请填写昵称(区分多个同类型网盘)",trigger:"blur"}],promotion_account:[{required:!0,message:"请填写推广平台及账号",trigger:"blur"}]})),J=h(()=>Object.entries(x)),Q=h(()=>{if(!l.cloud_type)return"请先选择网盘类型";const t=l.cloud_type;return t==="quark"||t==="baidu"?`请输入 ${x[t]||t} 的完整 Cookie`:b.value?"留空则保持原有":"输入完整 Cookie"}),W=h(()=>x[l.cloud_type]||l.cloud_type||""),X=h(()=>{const t=l.cloud_type;return t?{quark:`<li>在电脑上打开 <a href="https://pan.quark.cn" target="_blank">pan.quark.cn</a> 并登录你的夸克账号</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → 切换到 <strong>网络 (Network)</strong> 选项卡</li>
|
||||
<li>刷新页面,在请求列表中点击任意一个请求(如 <code>account/info</code>)</li>
|
||||
<li>在右侧 <strong>请求头 (Request Headers)</strong> 中找到 <code>Cookie</code> 字段</li>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-hm1O9sxT.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CK-9TfWb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ekbe64zQ.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="时间" width="140">
|
||||
<el-table-column label="时间" min-width="155">
|
||||
<template #default="{ row }">
|
||||
<span :title="row.created_at">{{ formatTime(row.created_at) }}</span>
|
||||
</template>
|
||||
@@ -192,6 +192,12 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="推广账号" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.promotion_account || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="状态" width="72" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="statusTip(row.status)" placement="top">
|
||||
|
||||
@@ -828,7 +828,6 @@ const searchAllChannels = computed({
|
||||
set: (val: boolean) => { configs.search_all_channels = val ? 'true' : 'false' },
|
||||
})
|
||||
|
||||
const autoUpdateEnabled = computed({
|
||||
get: () => String(configs.auto_update_enabled) === 'true',
|
||||
set: (val: boolean) => { configs.auto_update_enabled = val ? 'true' : 'false' },
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import{x as qu,m as $_,h as Ce,B as Hg,d as q_,o as K_,a as Tt,c as Nt,K as ml,L as _l,b as Q,F as Vr,r as Gr,f as $t,w as qt,e as Oe,v as Ka,j as Hr,i as Q_,t as wt,n as Nc,y as Ei,l as zc,p as J_,E as j_,u as t1}from"./index-hm1O9sxT.js";import{a as e1,h as r1,c as i1,i as n1,j as a1,t as o1,_ as s1}from"./_plugin-vue_export-helper-D4DENoBS.js";import l1 from"./CloudConfig-Dl6za0gm.js";import u1 from"./SystemConfig-CwYIM6EH.js";import f1 from"./SaveRecords-DQrV5BWq.js";import"./index-Bn7NwETH.js";import"./CloudBadge-CNjyd2IF.js";/*! *****************************************************************************
|
||||
import{x as qu,m as $_,h as Ce,B as Hg,d as q_,o as K_,a as Tt,c as Nt,K as ml,L as _l,b as Q,F as Vr,r as Gr,f as $t,w as qt,e as Oe,v as Ka,j as Hr,i as Q_,t as wt,n as Nc,y as Ei,l as zc,p as J_,E as j_,u as t1}from"./index-CK-9TfWb.js";import{a as e1,h as r1,c as i1,i as n1,j as a1,t as o1,_ as s1}from"./_plugin-vue_export-helper-D4DENoBS.js";import l1 from"./CloudConfig-vPJUzF1U.js";import u1 from"./SystemConfig-tnevz2yA.js";import f1 from"./SaveRecords-BcXS42JB.js";import"./index-Bn7NwETH.js";import"./CloudBadge-OCEQ2GP-.js";/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
@@ -1 +1 @@
|
||||
import{d as B,o as N,a as V,c as I,b as n,t as c,f as e,w as t,h as g,v as y,j as u,k as r,C as M,l,D as T,G as j,H as q,I as z,J as A,u as D,z as H}from"./index-hm1O9sxT.js";import{a as L,_ as R}from"./_plugin-vue_export-helper-D4DENoBS.js";const E={class:"admin-layout"},G={class:"admin-sidebar"},J={class:"sidebar-brand"},W={class:"sidebar-brand-text"},F={class:"sidebar-version"},K={class:"admin-content"},O={class:"content-header"},P={class:"content-breadcrumb"},Q={class:"breadcrumb-current"},U={class:"content-actions"},X={class:"content-body"},Y=B({__name:"AdminLayout",setup(Z){const d=D(),f=H(),m=g(""),_=g(""),b={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},p=y(()=>{const o=f.name;return o==="admin-cloud-configs"?"cloud-configs-toggle":o==="admin-cleanup"?"cloud-configs-cleanup":o==="admin-system"?f.query.section||"sys-site":o==="admin-save-records"?"save-records":"dashboard"}),x=y(()=>b[p.value]||"仪表盘");function w(o){o==="dashboard"?d.push("/admin/dashboard"):o==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):o==="cloud-configs-cleanup"?d.push("/admin/cleanup"):o.startsWith("sys-")?d.push({path:"/admin/system",query:{section:o}}):o==="save-records"?d.push("/admin/save-records"):o==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function h(){d.push("/")}return N(async()=>{try{const o=await L();m.value=o.site_name||""}catch{}try{const s=await(await fetch("/health")).json();_.value=s.version}catch{}}),(o,s)=>{const i=u("el-icon"),a=u("el-menu-item"),v=u("el-sub-menu"),C=u("el-menu"),k=u("el-button"),S=u("router-view");return V(),I("div",E,[n("aside",G,[n("div",J,[s[1]||(s[1]=n("div",{class:"sidebar-logo"},"☁️",-1)),n("div",W,[n("h2",null,c(m.value||"CloudSearch"),1),s[0]||(s[0]=n("p",null,"管理控制台",-1))])]),e(C,{"default-active":p.value,class:"sidebar-menu",onSelect:w},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=n("span",null,"仪表盘",-1))]),_:1}),e(v,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=n("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(v,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=n("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(q))]),_:1}),s[13]||(s[13]=n("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=n("div",{class:"sidebar-spacer"},null,-1)),n("div",F,"v"+c(_.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(z))]),_:1}),s[14]||(s[14]=n("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),n("div",K,[n("header",O,[n("div",P,[n("span",Q,c(x.value),1)]),n("div",U,[e(k,{text:"",size:"small",onClick:h},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),n("main",X,[e(S)])])])}}}),es=R(Y,[["__scopeId","data-v-647abf08"]]);export{es as default};
|
||||
import{d as B,o as N,a as V,c as I,b as n,t as c,f as e,w as t,h as g,v as y,j as u,k as r,C as M,l,D as T,G as j,H as q,I as z,J as A,u as D,z as H}from"./index-CK-9TfWb.js";import{a as L,_ as R}from"./_plugin-vue_export-helper-D4DENoBS.js";const E={class:"admin-layout"},G={class:"admin-sidebar"},J={class:"sidebar-brand"},W={class:"sidebar-brand-text"},F={class:"sidebar-version"},K={class:"admin-content"},O={class:"content-header"},P={class:"content-breadcrumb"},Q={class:"breadcrumb-current"},U={class:"content-actions"},X={class:"content-body"},Y=B({__name:"AdminLayout",setup(Z){const d=D(),f=H(),m=g(""),_=g(""),b={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},p=y(()=>{const o=f.name;return o==="admin-cloud-configs"?"cloud-configs-toggle":o==="admin-cleanup"?"cloud-configs-cleanup":o==="admin-system"?f.query.section||"sys-site":o==="admin-save-records"?"save-records":"dashboard"}),x=y(()=>b[p.value]||"仪表盘");function w(o){o==="dashboard"?d.push("/admin/dashboard"):o==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):o==="cloud-configs-cleanup"?d.push("/admin/cleanup"):o.startsWith("sys-")?d.push({path:"/admin/system",query:{section:o}}):o==="save-records"?d.push("/admin/save-records"):o==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function h(){d.push("/")}return N(async()=>{try{const o=await L();m.value=o.site_name||""}catch{}try{const s=await(await fetch("/health")).json();_.value=s.version}catch{}}),(o,s)=>{const i=u("el-icon"),a=u("el-menu-item"),v=u("el-sub-menu"),C=u("el-menu"),k=u("el-button"),S=u("router-view");return V(),I("div",E,[n("aside",G,[n("div",J,[s[1]||(s[1]=n("div",{class:"sidebar-logo"},"☁️",-1)),n("div",W,[n("h2",null,c(m.value||"CloudSearch"),1),s[0]||(s[0]=n("p",null,"管理控制台",-1))])]),e(C,{"default-active":p.value,class:"sidebar-menu",onSelect:w},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=n("span",null,"仪表盘",-1))]),_:1}),e(v,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=n("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(v,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=n("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(q))]),_:1}),s[13]||(s[13]=n("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=n("div",{class:"sidebar-spacer"},null,-1)),n("div",F,"v"+c(_.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(z))]),_:1}),s[14]||(s[14]=n("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),n("div",K,[n("header",O,[n("div",P,[n("span",Q,c(x.value),1)]),n("div",U,[e(k,{text:"",size:"small",onClick:h},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),n("main",X,[e(S)])])])}}}),es=R(Y,[["__scopeId","data-v-647abf08"]]);export{es as default};
|
||||
@@ -1 +1 @@
|
||||
import{d as k,o as C,a as w,c as y,b as a,t as m,f as t,w as i,g as x,e as L,h as d,j as p,l as N,i as S,E as B}from"./index-hm1O9sxT.js";import{a as E,d as M,_ as U}from"./_plugin-vue_export-helper-D4DENoBS.js";const j={class:"admin-login-page"},q={class:"login-card"},A={class:"login-brand"},I={class:"login-title"},K={key:0,class:"error-msg"},R={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const f=d(),u=d(!1),c=d(""),g=d(""),v=d("");E().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=f.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await M(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return w(),y("div",j,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",q,[a("div",A,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",I,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:f,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=_=>s.username=_),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=_=>s.password=_),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(w(),y("p",K,m(c.value),1)):L("",!0),a("p",R,"CloudSearch v"+m(v.value),1)])])}}}),G=U(z,[["__scopeId","data-v-bd0b6672"]]);export{G as default};
|
||||
import{d as k,o as C,a as w,c as y,b as a,t as m,f as t,w as i,g as x,e as L,h as d,j as p,l as N,i as S,E as B}from"./index-CK-9TfWb.js";import{a as E,d as M,_ as U}from"./_plugin-vue_export-helper-D4DENoBS.js";const j={class:"admin-login-page"},q={class:"login-card"},A={class:"login-brand"},I={class:"login-title"},K={key:0,class:"error-msg"},R={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const f=d(),u=d(!1),c=d(""),g=d(""),v=d("");E().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=f.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await M(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return w(),y("div",j,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",q,[a("div",A,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",I,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:f,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=_=>s.username=_),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=_=>s.password=_),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(w(),y("p",K,m(c.value),1)):L("",!0),a("p",R,"CloudSearch v"+m(v.value),1)])])}}}),G=U(z,[["__scopeId","data-v-bd0b6672"]]);export{G as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{C as s,b as a,a as n}from"./index-Bn7NwETH.js";import{d as l,a as t,c,p as d,k as o,e as r,l as u,t as m}from"./index-hm1O9sxT.js";import{_}from"./_plugin-vue_export-helper-D4DENoBS.js";const p=["src"],i=l({__name:"CloudBadge",props:{cloud_type:{},showIcon:{type:Boolean}},setup(e){return(C,y)=>(t(),c("span",{class:"cloud-badge",style:d({background:o(s)[e.cloud_type]})},[e.showIcon&&o(a)[e.cloud_type]?(t(),c("img",{key:0,src:o(a)[e.cloud_type],class:"badge-icon"},null,8,p)):r("",!0),u(" "+m(o(n)[e.cloud_type]),1)],4))}}),L=_(i,[["__scopeId","data-v-9106805f"]]);export{L as C};
|
||||
import{C as s,b as a,a as n}from"./index-Bn7NwETH.js";import{d as l,a as t,c,p as d,k as o,e as r,l as u,t as m}from"./index-CK-9TfWb.js";import{_}from"./_plugin-vue_export-helper-D4DENoBS.js";const p=["src"],i=l({__name:"CloudBadge",props:{cloud_type:{},showIcon:{type:Boolean}},setup(e){return(C,y)=>(t(),c("span",{class:"cloud-badge",style:d({background:o(s)[e.cloud_type]})},[e.showIcon&&o(a)[e.cloud_type]?(t(),c("img",{key:0,src:o(a)[e.cloud_type],class:"badge-icon"},null,8,p)):r("",!0),u(" "+m(o(n)[e.cloud_type]),1)],4))}}),L=_(i,[["__scopeId","data-v-9106805f"]]);export{L as C};
|
||||
@@ -1,4 +1,4 @@
|
||||
import{d as ke,o as L,m as ve,E as _,a as c,c as k,f as n,w as a,b as r,h as C,j as p,i as be,F as R,r as K,t as v,y as g,l as d,e as A,k as Ce,M as he,p as xe,n as H,K as Be,L as Te,v as h}from"./index-hm1O9sxT.js";import{a as x}from"./index-Bn7NwETH.js";import{c as we,k as Fe,h as Ne,l as G,t as Ve,u as P,m as ze,n as Se,o as $e,_ as Ue}from"./_plugin-vue_export-helper-D4DENoBS.js";import{C as De}from"./CloudBadge-CNjyd2IF.js";const Ie={class:"cloud-config"},Me={class:"cloud-toggle-grid"},Oe=["src"],qe={class:"cloud-label"},Ee={class:"toolbar"},Le={key:0,class:"nickname-text"},Re={key:0,class:"promotion-text"},Ke={key:0,class:"uid-cell"},Ae={key:0,class:"verifying"},He={key:0,class:"storage-cell"},Ge={class:"storage-bar-wrap"},Pe={class:"storage-text"},je={class:"storage-used"},Je={class:"storage-total"},Qe={class:"storage-free"},We={key:0,class:"save-count"},Xe={style:{"line-height":"1.6"}},Ye={class:"cookie-tips-header"},Ze={class:"cookie-tips-title"},et=["innerHTML"],tt=ke({__name:"CloudConfig",setup(ot){const z=C([]),D=C(),F=C([]),B=C(!1),T=C(!1),b=C(null),l=be({cloud_type:"",nickname:"",promotion_account:"",is_transfer_enabled:!1,cookie:"",_verifying:!1,_storageUsed:"",_storageTotal:""}),j=h(()=>({cloud_type:[{required:!0,message:"请选择网盘类型",trigger:"change"}],nickname:[{required:!1,message:"请填写昵称(区分多个同类型网盘)",trigger:"blur"}],promotion_account:[{required:!0,message:"请填写推广平台及账号",trigger:"blur"}]})),J=h(()=>Object.entries(x)),Q=h(()=>{if(!l.cloud_type)return"请先选择网盘类型";const t=l.cloud_type;return t==="quark"||t==="baidu"?`请输入 ${x[t]||t} 的完整 Cookie`:b.value?"留空则保持原有":"输入完整 Cookie"}),W=h(()=>x[l.cloud_type]||l.cloud_type||""),X=h(()=>{const t=l.cloud_type;return t?{quark:`<li>在电脑上打开 <a href="https://pan.quark.cn" target="_blank">pan.quark.cn</a> 并登录你的夸克账号</li>
|
||||
import{d as ke,o as L,m as ve,E as _,a as c,c as k,f as n,w as a,b as r,h as C,j as p,i as be,F as R,r as K,t as v,y as g,l as d,e as A,k as Ce,M as he,p as xe,n as H,K as Be,L as Te,v as h}from"./index-CK-9TfWb.js";import{a as x}from"./index-Bn7NwETH.js";import{c as we,k as Fe,h as Ne,l as G,t as Ve,u as P,m as ze,n as Se,o as $e,_ as Ue}from"./_plugin-vue_export-helper-D4DENoBS.js";import{C as De}from"./CloudBadge-OCEQ2GP-.js";const Ie={class:"cloud-config"},Me={class:"cloud-toggle-grid"},Oe=["src"],qe={class:"cloud-label"},Ee={class:"toolbar"},Le={key:0,class:"nickname-text"},Re={key:0,class:"promotion-text"},Ke={key:0,class:"uid-cell"},Ae={key:0,class:"verifying"},He={key:0,class:"storage-cell"},Ge={class:"storage-bar-wrap"},Pe={class:"storage-text"},je={class:"storage-used"},Je={class:"storage-total"},Qe={class:"storage-free"},We={key:0,class:"save-count"},Xe={style:{"line-height":"1.6"}},Ye={class:"cookie-tips-header"},Ze={class:"cookie-tips-title"},et=["innerHTML"],tt=ke({__name:"CloudConfig",setup(ot){const z=C([]),D=C(),F=C([]),B=C(!1),T=C(!1),b=C(null),l=be({cloud_type:"",nickname:"",promotion_account:"",is_transfer_enabled:!1,cookie:"",_verifying:!1,_storageUsed:"",_storageTotal:""}),j=h(()=>({cloud_type:[{required:!0,message:"请选择网盘类型",trigger:"change"}],nickname:[{required:!1,message:"请填写昵称(区分多个同类型网盘)",trigger:"blur"}],promotion_account:[{required:!0,message:"请填写推广平台及账号",trigger:"blur"}]})),J=h(()=>Object.entries(x)),Q=h(()=>{if(!l.cloud_type)return"请先选择网盘类型";const t=l.cloud_type;return t==="quark"||t==="baidu"?`请输入 ${x[t]||t} 的完整 Cookie`:b.value?"留空则保持原有":"输入完整 Cookie"}),W=h(()=>x[l.cloud_type]||l.cloud_type||""),X=h(()=>{const t=l.cloud_type;return t?{quark:`<li>在电脑上打开 <a href="https://pan.quark.cn" target="_blank">pan.quark.cn</a> 并登录你的夸克账号</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → 切换到 <strong>网络 (Network)</strong> 选项卡</li>
|
||||
<li>刷新页面,在请求列表中点击任意一个请求(如 <code>account/info</code>)</li>
|
||||
<li>在右侧 <strong>请求头 (Request Headers)</strong> 中找到 <code>Cookie</code> 字段</li>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
source_clean/frontend/assets/SaveRecords-BBwQkCBh.css
Normal file
1
source_clean/frontend/assets/SaveRecords-BBwQkCBh.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-hm1O9sxT.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CK-9TfWb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ekbe64zQ.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -48,6 +48,9 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
const db = getDb();
|
||||
const ipLocation = await lookupIpLocation(ipAddress || '');
|
||||
|
||||
// ── Track if this is a re-save after link was found invalid
|
||||
let retrySave = false;
|
||||
|
||||
// ── Short-term dedup: prevent duplicate saves of the same URL within 60 seconds ──
|
||||
const DEDUP_WINDOW_SEC = 60;
|
||||
let dedupCutoff = '';
|
||||
@@ -58,17 +61,33 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
dedupCutoff = recentCutoff.cutoff;
|
||||
|
||||
const recentRecord = db.prepare(
|
||||
`SELECT share_url, share_pwd, status, error_message, folder_name, original_folder_name FROM save_records
|
||||
`SELECT share_url, share_pwd, status, file_size, error_message, folder_name, original_folder_name FROM save_records
|
||||
WHERE source_url = ? AND created_at >= ?
|
||||
ORDER BY created_at DESC LIMIT 1`
|
||||
).get(shareUrl, dedupCutoff) as {
|
||||
share_url: string | null; share_pwd: string | null; status: string;
|
||||
share_url: string | null; share_pwd: string | null; status: string; file_size: string | null;
|
||||
error_message: string | null; folder_name: string | null; original_folder_name: string | null;
|
||||
} | undefined;
|
||||
|
||||
if (recentRecord) {
|
||||
const alreadySaved = recentRecord.status === 'success' || recentRecord.status === 'reused';
|
||||
if (alreadySaved && recentRecord.share_url) {
|
||||
// Validate the cached link before returning — avoid returning an invalid link
|
||||
let dedupLinkInvalid = false;
|
||||
try {
|
||||
const { LinkValidator: DedupLinkValidator } = await import('../validation/link-validator.service');
|
||||
const dedupValidator = new DedupLinkValidator();
|
||||
const dedupValidation = await dedupValidator.validate(recentRecord.share_url, 'quark');
|
||||
if (dedupValidation.status !== 'valid') {
|
||||
dedupLinkInvalid = true;
|
||||
console.log(`[Share] 🛡️ Dedup link invalid (${dedupValidation.message}), falling through to normal save`);
|
||||
retrySave = true;
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`[Share] 🛡️ Dedup validation error: ${err.message}, falling through`);
|
||||
dedupLinkInvalid = true;
|
||||
}
|
||||
if (!dedupLinkInvalid) {
|
||||
console.log(`[Share] 🛡️ Dedup: ${shareUrl} was saved ${DEDUP_WINDOW_SEC}s ago (status=${recentRecord.status}), returning existing share link`);
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
@@ -76,7 +95,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
).run(
|
||||
cloudType, sourceTitle || null, shareUrl, cloudType,
|
||||
recentRecord.share_url, recentRecord.share_pwd || null,
|
||||
null, 0, 0, 0, 'reused', null,
|
||||
recentRecord.file_size || null, 0, 0, 0, 'reused', null,
|
||||
recentRecord.folder_name || null, recentRecord.original_folder_name || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
);
|
||||
@@ -89,6 +108,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`[Share] Dedup check failed: ${err.message}, proceeding with normal save`);
|
||||
}
|
||||
@@ -98,10 +118,10 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
if (reuseEnabled !== 'false') {
|
||||
try {
|
||||
const existing = db.prepare(
|
||||
`SELECT share_url, share_pwd, folder_name, original_folder_name FROM save_records
|
||||
`SELECT share_url, share_pwd, file_size, folder_name, original_folder_name FROM save_records
|
||||
WHERE source_url = ? AND status IN ('success', 'reused') AND share_url IS NOT NULL AND share_url != ''
|
||||
ORDER BY created_at DESC LIMIT 1`
|
||||
).get(shareUrl) as { share_url: string; share_pwd: string; folder_name: string | null; original_folder_name: string | null } | undefined;
|
||||
).get(shareUrl) as { share_url: string; share_pwd: string; file_size: string | null; folder_name: string | null; original_folder_name: string | null } | undefined;
|
||||
|
||||
if (existing?.share_url) {
|
||||
const { LinkValidator } = await import('../validation/link-validator.service');
|
||||
@@ -123,7 +143,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
).run(
|
||||
cloudType, sourceTitle || null, shareUrl, cloudType,
|
||||
existing.share_url, existing.share_pwd || null,
|
||||
null, 0, 0, 0, reuseStatus, null,
|
||||
existing.file_size || null, 0, 0, 0, reuseStatus, null,
|
||||
existing.folder_name || null, existing.original_folder_name || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
);
|
||||
@@ -134,6 +154,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
file_count: 0, folder_count: 0, duration_ms: 0,
|
||||
};
|
||||
}
|
||||
retrySave = true;
|
||||
console.log(`[Share] Existing share link for ${shareUrl} is invalid/expired, will re-save`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -155,7 +176,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
switch (cloudType) {
|
||||
case 'quark': {
|
||||
const driver = new QuarkDriver({ cookie: config.cookie!, nickname: config.nickname });
|
||||
driverResult = await driver.saveFromShare(shareUrl, sourceTitle);
|
||||
driverResult = await driver.saveFromShare(shareUrl, sourceTitle, retrySave);
|
||||
break;
|
||||
}
|
||||
case 'baidu': {
|
||||
@@ -215,12 +236,12 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, config_id, promotion_account, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || driverResult.folderName || null, shareUrl, cloudType,
|
||||
cloudType, sourceTitle || driverResult.folderName || null, shareUrl, cloudType, config.id, config.promotion_account || null,
|
||||
driverResult.shareUrl || null, driverResult.sharePwd || null,
|
||||
null, driverResult.fileCount || 0, driverResult.folderCount || 0,
|
||||
(driverResult as any).fileSize || null, driverResult.fileCount || 0, driverResult.folderCount || 0,
|
||||
durationMs, driverResult.success ? 'success' : 'failed',
|
||||
driverResult.success ? null : driverResult.message,
|
||||
driverResult.folderName || null, driverResult.originalFolderName || null,
|
||||
@@ -247,9 +268,9 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
).run(config.id);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_url, target_cloud, duration_ms, status, error_message, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(cloudType, shareUrl, cloudType, durationMs, 'failed', errorMessage, ipAddress || null, ipLocation, localTimestamp());
|
||||
`INSERT INTO save_records (source_type, source_url, target_cloud, config_id, promotion_account, duration_ms, status, error_message, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(cloudType, shareUrl, cloudType, config.id, config.promotion_account || null, durationMs, 'failed', errorMessage, ipAddress || null, ipLocation, localTimestamp());
|
||||
|
||||
return { success: false, message: errorMessage };
|
||||
}
|
||||
|
||||
389
source_clean/src/cloud/cloud.service.ts.bak3
Normal file
389
source_clean/src/cloud/cloud.service.ts.bak3
Normal file
@@ -0,0 +1,389 @@
|
||||
import { getDb } from '../database/database';
|
||||
import { localTimestamp, formatLocalDateTime } from '../utils/time';
|
||||
import { getSystemConfig } from '../admin/system-config.service';
|
||||
import { QuarkDriver } from './drivers/quark.driver';
|
||||
import { BaiduDriver } from './drivers/baidu.driver';
|
||||
import { CloudConfig, getAndValidateCredential, getActiveCloudConfigs } from './credential.service';
|
||||
import { lookupIpLocation } from './ip-lookup';
|
||||
import { notifyConfigEvent } from './notification.service';
|
||||
|
||||
/** In-flight save dedup: prevents concurrent saves of the same URL (race condition fix) */
|
||||
const inFlightSaves = new Map<string, Promise<SaveResult>>();
|
||||
|
||||
export interface SaveResult {
|
||||
success: boolean;
|
||||
shareUrl?: string;
|
||||
share_url?: string;
|
||||
sharePwd?: string;
|
||||
folderName?: string;
|
||||
message: string;
|
||||
file_count?: number;
|
||||
folder_count?: number;
|
||||
duration_ms?: number;
|
||||
}
|
||||
|
||||
export interface SaveRecord {
|
||||
id: number;
|
||||
source_type: string;
|
||||
source_title: string | null;
|
||||
source_url: string;
|
||||
target_cloud: string;
|
||||
share_url: string | null;
|
||||
share_pwd: string | null;
|
||||
file_size: string | null;
|
||||
file_count: number;
|
||||
folder_count: number;
|
||||
duration_ms: number;
|
||||
status: string;
|
||||
error_message: string | null;
|
||||
folder_name: string | null;
|
||||
original_folder_name: string | null;
|
||||
ip_address: string | null;
|
||||
ip_location: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/** Core save logic extracted so inFlight dedup can wrap it */
|
||||
async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?: string, ipAddress?: string): Promise<SaveResult> {
|
||||
const db = getDb();
|
||||
const ipLocation = await lookupIpLocation(ipAddress || '');
|
||||
|
||||
// ── Short-term dedup: prevent duplicate saves of the same URL within 60 seconds ──
|
||||
const DEDUP_WINDOW_SEC = 60;
|
||||
let dedupCutoff = '';
|
||||
try {
|
||||
const recentCutoff = db.prepare(
|
||||
`SELECT datetime('now','localtime', '-${DEDUP_WINDOW_SEC} seconds') as cutoff`
|
||||
).get() as { cutoff: string };
|
||||
dedupCutoff = recentCutoff.cutoff;
|
||||
|
||||
const recentRecord = db.prepare(
|
||||
`SELECT share_url, share_pwd, status, file_size, error_message, folder_name, original_folder_name FROM save_records
|
||||
WHERE source_url = ? AND created_at >= ?
|
||||
ORDER BY created_at DESC LIMIT 1`
|
||||
).get(shareUrl, dedupCutoff) as {
|
||||
share_url: string | null; share_pwd: string | null; status: string; file_size: string | null;
|
||||
error_message: string | null; folder_name: string | null; original_folder_name: string | null;
|
||||
} | undefined;
|
||||
|
||||
if (recentRecord) {
|
||||
const alreadySaved = recentRecord.status === 'success' || recentRecord.status === 'reused';
|
||||
if (alreadySaved && recentRecord.share_url) {
|
||||
console.log(`[Share] 🛡️ Dedup: ${shareUrl} was saved ${DEDUP_WINDOW_SEC}s ago (status=${recentRecord.status}), returning existing share link`);
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || null, shareUrl, cloudType,
|
||||
recentRecord.share_url, recentRecord.share_pwd || null,
|
||||
recentRecord.file_size || null, 0, 0, 0, 'reused', null,
|
||||
recentRecord.folder_name || null, recentRecord.original_folder_name || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
message: `🛡️ 此资源刚在 ${DEDUP_WINDOW_SEC} 秒内转存过,直接返回已有分享链接`,
|
||||
share_url: recentRecord.share_url, shareUrl: recentRecord.share_url,
|
||||
sharePwd: recentRecord.share_pwd || '', folderName: '',
|
||||
file_count: 0, folder_count: 0, duration_ms: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`[Share] Dedup check failed: ${err.message}, proceeding with normal save`);
|
||||
}
|
||||
|
||||
// ── Share link reuse: if same source URL was already saved successfully, validate and reuse ──
|
||||
const reuseEnabled = getSystemConfig('save_reuse_enabled');
|
||||
if (reuseEnabled !== 'false') {
|
||||
try {
|
||||
const existing = db.prepare(
|
||||
`SELECT share_url, share_pwd, file_size, folder_name, original_folder_name FROM save_records
|
||||
WHERE source_url = ? AND status IN ('success', 'reused') AND share_url IS NOT NULL AND share_url != ''
|
||||
ORDER BY created_at DESC LIMIT 1`
|
||||
).get(shareUrl) as { share_url: string; share_pwd: string; file_size: string | null; folder_name: string | null; original_folder_name: string | null } | undefined;
|
||||
|
||||
if (existing?.share_url) {
|
||||
const { LinkValidator } = await import('../validation/link-validator.service');
|
||||
const validator = new LinkValidator();
|
||||
const validation = await validator.validate(existing.share_url, 'quark');
|
||||
if (validation.status === 'valid') {
|
||||
const isFirstReuse = dedupCutoff ? !db.prepare(
|
||||
`SELECT 1 FROM save_records WHERE source_url = ? AND created_at >= ? AND status = 'reused' LIMIT 1`
|
||||
).get(shareUrl, dedupCutoff) : true;
|
||||
const reuseStatus = isFirstReuse ? 'success' : 'reused';
|
||||
const reuseMsg = isFirstReuse
|
||||
? `♻️ 检测到此资源之前已转存过,直接复用已存在的分享链接`
|
||||
: `♻️ 短时间内重复请求,复用已有分享链接`;
|
||||
|
||||
console.log(`[Share] ♻️ Reusing existing share link for ${shareUrl}: ${existing.share_url} (firstReuse=${isFirstReuse})`);
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || null, shareUrl, cloudType,
|
||||
existing.share_url, existing.share_pwd || null,
|
||||
existing.file_size || null, 0, 0, 0, reuseStatus, null,
|
||||
existing.folder_name || null, existing.original_folder_name || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
);
|
||||
return {
|
||||
success: true, message: reuseMsg,
|
||||
share_url: existing.share_url, shareUrl: existing.share_url,
|
||||
sharePwd: existing.share_pwd || '', folderName: '',
|
||||
file_count: 0, folder_count: 0, duration_ms: 0,
|
||||
};
|
||||
}
|
||||
console.log(`[Share] Existing share link for ${shareUrl} is invalid/expired, will re-save`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`[Share] Link reuse check failed: ${err.message}, proceeding with normal save`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Unified credential validation ──
|
||||
const credential = await getAndValidateCredential(cloudType);
|
||||
if (!credential.valid || !credential.config) {
|
||||
return { success: false, message: credential.message };
|
||||
}
|
||||
const config = credential.config;
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
let driverResult: { success: boolean; message: string; shareUrl?: string; sharePwd?: string; folderName?: string; fileCount?: number; folderCount?: number; originalFolderName?: string };
|
||||
|
||||
switch (cloudType) {
|
||||
case 'quark': {
|
||||
const driver = new QuarkDriver({ cookie: config.cookie!, nickname: config.nickname });
|
||||
driverResult = await driver.saveFromShare(shareUrl, sourceTitle);
|
||||
break;
|
||||
}
|
||||
case 'baidu': {
|
||||
const driver = new BaiduDriver({ cookie: config.cookie!, nickname: config.nickname });
|
||||
driverResult = await driver.saveFromShare(shareUrl, sourceTitle);
|
||||
break;
|
||||
}
|
||||
case 'aliyun':
|
||||
return { success: false, message: '阿里云盘保存功能暂未实现' };
|
||||
default:
|
||||
return { success: false, message: `暂不支持 ${cloudType} 的保存功能` };
|
||||
}
|
||||
|
||||
const durationMs = Date.now() - startTime;
|
||||
|
||||
if (driverResult.success) {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET last_used_at = datetime('now','localtime'), total_saves = total_saves + 1, consecutive_failures = 0 WHERE id = ?`
|
||||
).run(config.id);
|
||||
const nickname = config.nickname || cloudType;
|
||||
notifyConfigEvent(config.id, 'save_success', `✅ 转存成功`, `**${cloudType}** · ${nickname}
|
||||
文件: ${driverResult.folderName || sourceTitle || shareUrl}
|
||||
耗时: ${((Date.now() - startTime) / 1000).toFixed(1)}s`, 'info', {
|
||||
file_name: driverResult.folderName || sourceTitle || shareUrl || '',
|
||||
file_size: '',
|
||||
cloud_type: cloudType,
|
||||
nickname: nickname || '',
|
||||
duration: ((Date.now() - startTime) / 1000).toFixed(1),
|
||||
share_url: shareUrl,
|
||||
});
|
||||
} else if ((driverResult as any).cookieExpired) {
|
||||
// Cookie expired — don't count as failure, user needs to re-login
|
||||
notifyConfigEvent(config.id, 'cookie_expire', `⚠️ Cookie过期`, `**${cloudType}** · ${config.nickname || '未知'}
|
||||
链接: ${shareUrl}
|
||||
请重新登录`, 'error', {
|
||||
cloud_type: cloudType,
|
||||
nickname: config.nickname || '',
|
||||
share_url: shareUrl,
|
||||
});
|
||||
} else {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET consecutive_failures = consecutive_failures + 1 WHERE id = ?`
|
||||
).run(config.id);
|
||||
const failCount = (db.prepare(`SELECT consecutive_failures FROM cloud_configs WHERE id = ?`).get(config.id) as any)?.consecutive_failures || 0;
|
||||
if (failCount >= 3) {
|
||||
notifyConfigEvent(config.id, 'save_fail', `❌ 转存连续失败 ${failCount} 次`, `**${cloudType}** · ${config.nickname || '未知'}
|
||||
链接: ${shareUrl}
|
||||
错误: ${driverResult.message}`, 'warn', {
|
||||
file_name: sourceTitle || shareUrl || '',
|
||||
fail_count: String(failCount),
|
||||
cloud_type: cloudType,
|
||||
nickname: config.nickname || '',
|
||||
error: driverResult.message || '',
|
||||
share_url: shareUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, config_id, promotion_account, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || driverResult.folderName || null, shareUrl, cloudType, config.id, config.promotion_account || null,
|
||||
driverResult.shareUrl || null, driverResult.sharePwd || null,
|
||||
(driverResult as any).fileSize || null, driverResult.fileCount || 0, driverResult.folderCount || 0,
|
||||
durationMs, driverResult.success ? 'success' : 'failed',
|
||||
driverResult.success ? null : driverResult.message,
|
||||
driverResult.folderName || null, driverResult.originalFolderName || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
);
|
||||
|
||||
return {
|
||||
success: driverResult.success,
|
||||
message: driverResult.message,
|
||||
share_url: driverResult.shareUrl || '',
|
||||
shareUrl: driverResult.shareUrl,
|
||||
sharePwd: (driverResult as any).sharePwd || '',
|
||||
folderName: driverResult.folderName || '',
|
||||
file_count: driverResult.fileCount || 0,
|
||||
folder_count: driverResult.folderCount || 0,
|
||||
duration_ms: durationMs,
|
||||
};
|
||||
} catch (err: any) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
const errorMessage = err.message || 'Failed to save to cloud';
|
||||
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET consecutive_failures = consecutive_failures + 1 WHERE id = ?`
|
||||
).run(config.id);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_url, target_cloud, config_id, promotion_account, duration_ms, status, error_message, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(cloudType, shareUrl, cloudType, config.id, config.promotion_account || null, durationMs, 'failed', errorMessage, ipAddress || null, ipLocation, localTimestamp());
|
||||
|
||||
return { success: false, message: errorMessage };
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveFromShare(shareUrl: string, cloudType: string, sourceTitle?: string, ipAddress?: string): Promise<SaveResult> {
|
||||
const key = `${cloudType}:${shareUrl}`;
|
||||
|
||||
const inflight = inFlightSaves.get(key);
|
||||
if (inflight) {
|
||||
console.log(`[Share] ⏳ In-flight: ${shareUrl} — another save is already running, awaiting result`);
|
||||
return inflight;
|
||||
}
|
||||
|
||||
const promise = doSaveFromShare(shareUrl, cloudType, sourceTitle, ipAddress);
|
||||
inFlightSaves.set(key, promise);
|
||||
try {
|
||||
return await promise;
|
||||
} finally {
|
||||
inFlightSaves.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save Records ──────────────────────────────────────────────────
|
||||
|
||||
export function getSaveRecords(page: number = 1, pageSize: number = 20, startDate?: string, endDate?: string, status?: string, sourceType?: string, keyword?: string): { total: number; records: SaveRecord[]; summary?: { total: number; success: number; failed: number; reused: number } } {
|
||||
const db = getDb();
|
||||
const offset = (page - 1) * pageSize;
|
||||
const conditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
const summaryConditions: string[] = [];
|
||||
const summaryParams: any[] = [];
|
||||
if (startDate) {
|
||||
conditions.push('created_at >= ?'); params.push(startDate);
|
||||
summaryConditions.push('created_at >= ?'); summaryParams.push(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
conditions.push('created_at < ?'); params.push(endDate);
|
||||
summaryConditions.push('created_at < ?'); summaryParams.push(endDate);
|
||||
}
|
||||
if (status) { conditions.push('status = ?'); params.push(status); }
|
||||
if (sourceType) {
|
||||
conditions.push('source_type = ?'); params.push(sourceType);
|
||||
summaryConditions.push('source_type = ?'); summaryParams.push(sourceType);
|
||||
}
|
||||
if (keyword) { conditions.push('source_title LIKE ?'); params.push(`%${keyword}%`); }
|
||||
const where = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
||||
const total = (db.prepare(`SELECT COUNT(*) as count FROM save_records ${where}`).get(...params) as any).count;
|
||||
const records = db.prepare(
|
||||
`SELECT * FROM save_records ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
||||
).all(...params, pageSize, offset) as SaveRecord[];
|
||||
|
||||
const summaryWhere = summaryConditions.length > 0 ? 'WHERE ' + summaryConditions.join(' AND ') : '';
|
||||
const summaryRows = db.prepare(
|
||||
`SELECT status, COUNT(*) as cnt FROM save_records ${summaryWhere} GROUP BY status`
|
||||
).all(...summaryParams) as { status: string; cnt: number }[];
|
||||
let sumTotal = 0, sumSuccess = 0, sumFailed = 0, sumReused = 0;
|
||||
for (const r of summaryRows) {
|
||||
sumTotal += r.cnt;
|
||||
if (r.status === 'success') sumSuccess = r.cnt;
|
||||
else if (r.status === 'failed') sumFailed = r.cnt;
|
||||
else if (r.status === 'reused') sumReused = r.cnt;
|
||||
}
|
||||
const summary = { total: sumTotal, success: sumSuccess, failed: sumFailed, reused: sumReused };
|
||||
|
||||
return { total, records, summary };
|
||||
}
|
||||
|
||||
export function cleanupOldSaveRecords(): void {
|
||||
const db = getDb();
|
||||
const cutoff = formatLocalDateTime(new Date(Date.now() - 60 * 24 * 60 * 60 * 1000));
|
||||
const deleted = db.prepare('DELETE FROM save_records WHERE created_at < ?').run(cutoff);
|
||||
console.log(`[Cleanup] Deleted ${deleted.changes} save records older than 60 days (before ${cutoff})`);
|
||||
}
|
||||
|
||||
// ── Storage Refresh ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Refresh storage info for all active cloud configs that have a getStorageInfo method.
|
||||
* Supports quark and baidu drivers.
|
||||
*/
|
||||
export async function refreshAllStorageInfo(): Promise<void> {
|
||||
const configs = getActiveCloudConfigs().filter(c => c.cookie);
|
||||
if (configs.length === 0) return;
|
||||
|
||||
// Driver mapping: cloud_type → { module, class }
|
||||
const DRIVER_REGISTRY: Record<string, { module: string; cls: string }> = {
|
||||
quark: { module: './drivers/quark.driver', cls: 'QuarkDriver' },
|
||||
baidu: { module: './drivers/baidu.driver', cls: 'BaiduDriver' },
|
||||
};
|
||||
|
||||
for (const cfg of configs) {
|
||||
const entry = DRIVER_REGISTRY[cfg.cloud_type];
|
||||
if (!entry) continue; // no getStorageInfo support for this cloud type
|
||||
|
||||
try {
|
||||
const mod = require(entry.module);
|
||||
const Driver = mod[entry.cls];
|
||||
if (!Driver) continue;
|
||||
|
||||
const driver = new Driver({ cookie: cfg.cookie, nickname: cfg.nickname });
|
||||
|
||||
// Try getStorageInfo (now uses fast /member API for accurate data)
|
||||
let storage: any;
|
||||
try {
|
||||
storage = await driver.getStorageInfo();
|
||||
} catch {
|
||||
if (typeof driver.getStorageInfoQuick === 'function') {
|
||||
storage = await driver.getStorageInfoQuick();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!storage) continue;
|
||||
|
||||
// Get formatted strings — some drivers return {used, total, usedBytes, totalBytes}
|
||||
const used = storage.used || '计算中...';
|
||||
const total = storage.total || '-';
|
||||
|
||||
// Only update if we got meaningful data
|
||||
const hasRealData =
|
||||
(storage.totalBytes > 0 || storage.usedBytes > 0) || // quark returns these
|
||||
(used !== '-' && used !== '0 B' && used !== '计算中...'); // baidu check
|
||||
|
||||
if (hasRealData) {
|
||||
const db = getDb();
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET storage_used = ?, storage_total = ? WHERE id = ?`
|
||||
).run(used, total, cfg.id);
|
||||
console.log(`[Storage] Refreshed ${cfg.cloud_type}#${cfg.id}: ${used} / ${total}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`[Storage] Failed to refresh ${cfg.cloud_type}#${cfg.id}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ export interface CloudConfig {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
verification_status?: string;
|
||||
promotion_account?: string;
|
||||
cloud_type_uid?: string;
|
||||
cookie_uid?: string;
|
||||
}
|
||||
|
||||
// ── Cookie UID Extraction ────────────────────────────────────────
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
import { getDb } from '../database/database';
|
||||
import { encrypt, decrypt, isEncrypted } from '../utils/crypto';
|
||||
import { localTimestamp, formatLocalDate, formatLocalDateTime } from '../utils/time';
|
||||
|
||||
export interface CloudConfig {
|
||||
id: number;
|
||||
cloud_type: string;
|
||||
cookie?: string;
|
||||
nickname?: string;
|
||||
is_active: number;
|
||||
storage_used?: string;
|
||||
storage_total?: string;
|
||||
checkin_status: string; // 'none'|'success'|'failed'|'pending'|'skipped'
|
||||
last_checkin_at?: string;
|
||||
checkin_message?: string;
|
||||
consecutive_failures: number;
|
||||
last_used_at?: string;
|
||||
total_saves: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
verification_status?: string;
|
||||
}
|
||||
|
||||
// ── Cookie UID Extraction ────────────────────────────────────────
|
||||
|
||||
function extractCookieUid(cookie: string): string {
|
||||
|
||||
function decryptCookie(encrypted: string): string {
|
||||
if (!encrypted) return '';
|
||||
if (!isEncrypted(encrypted)) return encrypted;
|
||||
return decrypt(encrypted);
|
||||
}
|
||||
if (!cookie) return '';
|
||||
let m = cookie.match(/__uid=([a-zA-Z0-9+/=_-]+)/);
|
||||
if (m) return m[1];
|
||||
m = cookie.match(/b-user-id=([a-zA-Z0-9-]+)/);
|
||||
if (m) return m[1];
|
||||
return '';
|
||||
}
|
||||
|
||||
// ── Config CRUD ──────────────────────────────────────────────────
|
||||
|
||||
export function getCloudConfigs(): CloudConfig[] {
|
||||
const db = getDb();
|
||||
return db.prepare(
|
||||
`SELECT id, cloud_type, cookie, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at, verification_status
|
||||
FROM cloud_configs ORDER BY id ASC`
|
||||
).all() as CloudConfig[];
|
||||
}
|
||||
|
||||
export function getAvailableClouds(): CloudConfig[] {
|
||||
const db = getDb();
|
||||
return db.prepare(
|
||||
`SELECT id, cloud_type, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at
|
||||
FROM cloud_configs WHERE is_active = 1 ORDER BY id ASC`
|
||||
).all() as CloudConfig[];
|
||||
}
|
||||
|
||||
/** Returns the first active config matching the given cloud type. */
|
||||
export function getCloudConfigByType(cloudType: string): CloudConfig | undefined {
|
||||
const db = getDb();
|
||||
const cfg = db.prepare(
|
||||
`SELECT id, cloud_type, cookie, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at, verification_status
|
||||
FROM cloud_configs WHERE cloud_type = ? AND is_active = 1
|
||||
ORDER BY id ASC LIMIT 1`
|
||||
).get(cloudType) as CloudConfig | undefined;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
export function getCloudConfigById(id: number): CloudConfig | undefined {
|
||||
const db = getDb();
|
||||
const cfg = db.prepare(
|
||||
`SELECT id, cloud_type, cookie, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at, verification_status
|
||||
FROM cloud_configs WHERE id = ?`
|
||||
).get(id) as CloudConfig | undefined;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/** Returns all active cloud configs (used by save flow for cloud type switching). */
|
||||
export function getActiveCloudConfigs(): CloudConfig[] {
|
||||
const db = getDb();
|
||||
return db.prepare(
|
||||
`SELECT id, cloud_type, cookie, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at
|
||||
FROM cloud_configs WHERE is_active = 1
|
||||
ORDER BY cloud_type ASC, id ASC`
|
||||
).all() as CloudConfig[];
|
||||
}
|
||||
|
||||
export function saveCloudConfig(data: {
|
||||
id?: number;
|
||||
cloud_type: string;
|
||||
cookie?: string;
|
||||
nickname?: string;
|
||||
cookie_uid?: string;
|
||||
promotion_account?: string;
|
||||
is_active?: number;
|
||||
storage_used?: string;
|
||||
storage_total?: string;
|
||||
}): CloudConfig {
|
||||
const db = getDb();
|
||||
|
||||
const cookieUidForUpdate = data.cookie ? extractCookieUid(data.cookie) : null;
|
||||
const encryptedCookie = data.cookie ? encrypt(data.cookie) : null;
|
||||
|
||||
if (data.id) {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET
|
||||
cloud_type = COALESCE(?, cloud_type),
|
||||
cookie = COALESCE(?, cookie),
|
||||
nickname = COALESCE(?, nickname),
|
||||
cookie_uid = COALESCE(?, cookie_uid),
|
||||
promotion_account = COALESCE(?, promotion_account),
|
||||
is_active = COALESCE(?, is_active),
|
||||
storage_used = COALESCE(?, storage_used),
|
||||
storage_total = COALESCE(?, storage_total),
|
||||
consecutive_failures = 0,
|
||||
updated_at = ?
|
||||
WHERE id = ?`
|
||||
).run(data.cloud_type, encryptedCookie, data.nickname || null, cookieUidForUpdate || null, data.promotion_account || null, data.is_active ?? 1, data.storage_used || null, data.storage_total || null, localTimestamp(), data.id);
|
||||
} else {
|
||||
const existing = db.prepare(
|
||||
'SELECT id, nickname FROM cloud_configs WHERE cloud_type = ? AND is_active = 1 LIMIT 1'
|
||||
).get(data.cloud_type) as any;
|
||||
if (existing) {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET
|
||||
cookie = COALESCE(?, cookie),
|
||||
nickname = COALESCE(?, nickname),
|
||||
cookie_uid = COALESCE(?, cookie_uid),
|
||||
promotion_account = COALESCE(?, promotion_account),
|
||||
is_active = COALESCE(?, is_active),
|
||||
storage_used = COALESCE(?, storage_used),
|
||||
storage_total = COALESCE(?, storage_total),
|
||||
consecutive_failures = 0,
|
||||
updated_at = ?
|
||||
WHERE id = ?`
|
||||
).run(encryptedCookie, data.nickname || null, cookieUidForUpdate || null, data.promotion_account || null, data.is_active ?? 1, data.storage_used || null, data.storage_total || null, localTimestamp(), existing.id);
|
||||
} else {
|
||||
db.prepare(
|
||||
'INSERT INTO cloud_configs (cloud_type, cookie, nickname, cookie_uid, promotion_account, is_active, storage_used, storage_total, consecutive_failures) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)'
|
||||
).run(data.cloud_type, encryptedCookie, data.nickname || null, cookieUidForUpdate || null, data.promotion_account || null, data.is_active ?? 1, data.storage_used || null, data.storage_total || null);
|
||||
}
|
||||
}
|
||||
|
||||
const savedId = data.id || (db.prepare('SELECT last_insert_rowid() as id').get() as any).id;
|
||||
return db.prepare(
|
||||
`SELECT id, cloud_type, cookie, nickname, is_active, storage_used, storage_total,
|
||||
checkin_status, last_checkin_at, checkin_message, consecutive_failures,
|
||||
last_used_at, total_saves, created_at, updated_at
|
||||
FROM cloud_configs WHERE id = ?`
|
||||
).get(savedId) as CloudConfig;
|
||||
}
|
||||
|
||||
export function deleteCloudConfig(id: number): boolean {
|
||||
const db = getDb();
|
||||
const result = db.prepare('DELETE FROM cloud_configs WHERE id = ?').run(id);
|
||||
return result.changes > 0;
|
||||
}
|
||||
|
||||
// ── Cookie Validation ────────────────────────────────────────────
|
||||
|
||||
async function fetchQuarkNickname(cookie: string): Promise<string | null> {
|
||||
const MAX_RETRIES = 2;
|
||||
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
const response = await fetch('https://pan.quark.cn/account/info', {
|
||||
headers: {
|
||||
'Cookie': cookie,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'https://pan.quark.cn/',
|
||||
},
|
||||
signal: AbortSignal.timeout(15000),
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json() as any;
|
||||
if (data?.data?.nickname) return data.data.nickname;
|
||||
} catch {
|
||||
if (attempt < MAX_RETRIES) {
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function testCloudConnection(id: number): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
nickname?: string;
|
||||
storage_used?: string;
|
||||
storage_total?: string;
|
||||
}> {
|
||||
const config = getCloudConfigById(id);
|
||||
if (!config) {
|
||||
return { success: false, message: 'Cloud config not found' };
|
||||
}
|
||||
|
||||
if (!config.cookie) {
|
||||
return { success: false, message: 'Cookie not configured' };
|
||||
}
|
||||
|
||||
try {
|
||||
let valid = false;
|
||||
let nickname = '';
|
||||
let storageUsed = config.storage_used || '';
|
||||
let storageTotal = config.storage_total || '';
|
||||
|
||||
if (config.cloud_type === 'baidu') {
|
||||
const { BaiduDriver } = require('./drivers/baidu.driver');
|
||||
const driver = new BaiduDriver({ cookie: config.cookie, nickname: config.nickname });
|
||||
valid = await driver.validate();
|
||||
if (valid) {
|
||||
const info = await driver.getUserInfo();
|
||||
if (info) {
|
||||
nickname = config.nickname || info.nickname || '百度网盘';
|
||||
const fmt = (b: number) => b >= 1024**3 ? (b/1024**3).toFixed(2)+' GB' : (b/1024**2).toFixed(2)+' MB';
|
||||
storageUsed = fmt(info.usedBytes);
|
||||
storageTotal = fmt(info.totalBytes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { QuarkDriver } = require('./drivers/quark.driver');
|
||||
const driver = new QuarkDriver({ cookie: config.cookie, nickname: config.nickname });
|
||||
valid = await driver.validate();
|
||||
if (valid) {
|
||||
nickname = config.nickname || (await fetchQuarkNickname(config.cookie)) || '夸克网盘';
|
||||
const storage = await driver.getStorageInfoQuick();
|
||||
storageTotal = (storage.total !== '-' && storage.total !== '0 B') ? storage.total : (config.storage_total || '');
|
||||
}
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
if (!valid) {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET verification_status = 'invalid', updated_at = ? WHERE id = ?`
|
||||
).run(localTimestamp(), id);
|
||||
return { success: false, message: '连接失败:Cookie 无效或已过期,或网络暂时异常' };
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET nickname = ?, storage_total = ?, storage_used = ?, is_active = 1, verification_status = 'valid', updated_at = ? WHERE id = ?`
|
||||
).run(nickname, storageTotal, storageUsed, localTimestamp(), id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '连接成功',
|
||||
nickname,
|
||||
storage_used: storageUsed,
|
||||
storage_total: storageTotal,
|
||||
};
|
||||
} catch (err: any) {
|
||||
try {
|
||||
const db = getDb();
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET verification_status = 'invalid', updated_at = ? WHERE id = ?`
|
||||
).run(localTimestamp(), id);
|
||||
} catch {}
|
||||
return { success: false, message: `连接失败:${err.message || '未知错误'}` };
|
||||
}
|
||||
}
|
||||
|
||||
export async function testCloudConnectionWithCookie(cloudType: string, cookie: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
nickname?: string;
|
||||
storage_used?: string;
|
||||
storage_total?: string;
|
||||
}> {
|
||||
try {
|
||||
const { QuarkDriver } = require('./drivers/quark.driver');
|
||||
const driver = new QuarkDriver({ cookie, nickname: '' });
|
||||
const valid = await driver.validate();
|
||||
if (!valid) {
|
||||
return { success: false, message: '连接失败:Cookie 无效或已过期' };
|
||||
}
|
||||
const nickname = (await fetchQuarkNickname(cookie)) || cloudType;
|
||||
const storage = await driver.getStorageInfo();
|
||||
return {
|
||||
success: true,
|
||||
message: '连接成功',
|
||||
nickname,
|
||||
storage_used: storage.used,
|
||||
storage_total: storage.total,
|
||||
};
|
||||
} catch (err: any) {
|
||||
return { success: false, message: `连接失败:${err.message || '未知错误'}` };
|
||||
}
|
||||
}
|
||||
|
||||
// ── Unified Credential Validation ─────────────────────────────────
|
||||
|
||||
export interface CredentialValidationResult {
|
||||
valid: boolean;
|
||||
config?: CloudConfig;
|
||||
errorCode?: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and validate a credential for the given cloud type.
|
||||
*
|
||||
* This is the unified entry point for all save/transfer operations.
|
||||
* It handles:
|
||||
* 1. Finding an active config with < 5 consecutive failures (round-robin)
|
||||
* 2. Validating cookie freshness via driver.validate()
|
||||
* 3. Returning structured result with error codes
|
||||
*
|
||||
* Reference: search-ucmao get_and_validate_credential() pattern.
|
||||
*/
|
||||
export async function getAndValidateCredential(cloudType: string): Promise<CredentialValidationResult> {
|
||||
const db = getDb();
|
||||
|
||||
const config = db.prepare(
|
||||
`SELECT * FROM cloud_configs
|
||||
WHERE cloud_type = ? AND is_active = 1
|
||||
AND consecutive_failures < 5
|
||||
ORDER BY last_used_at ASC NULLS FIRST
|
||||
LIMIT 1`
|
||||
).get(cloudType) as CloudConfig | undefined;
|
||||
|
||||
if (!config) {
|
||||
return {
|
||||
valid: false,
|
||||
errorCode: 'NO_AVAILABLE_DRIVE',
|
||||
message: `Cloud type "${cloudType}" is not configured or no available drives`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!config.cookie) {
|
||||
return {
|
||||
valid: false,
|
||||
errorCode: 'COOKIE_MISSING',
|
||||
message: `Cookie not configured for ${cloudType} drive #${config.id}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
let cookieValid = false;
|
||||
if (cloudType === 'baidu') {
|
||||
const { BaiduDriver } = require('./drivers/baidu.driver');
|
||||
const driver = new BaiduDriver({ cookie: config.cookie, nickname: config.nickname });
|
||||
cookieValid = await driver.validate();
|
||||
} else {
|
||||
const { QuarkDriver } = require('./drivers/quark.driver');
|
||||
const driver = new QuarkDriver({ cookie: config.cookie, nickname: config.nickname });
|
||||
cookieValid = await driver.validate();
|
||||
}
|
||||
|
||||
if (!cookieValid) {
|
||||
db.prepare(
|
||||
`UPDATE cloud_configs SET verification_status = 'invalid', updated_at = ? WHERE id = ?`
|
||||
).run(localTimestamp(), config.id);
|
||||
return {
|
||||
valid: false,
|
||||
errorCode: 'COOKIE_EXPIRED',
|
||||
message: `Cookie expired or invalid for ${cloudType} drive #${config.id}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
config,
|
||||
message: 'ok',
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {
|
||||
valid: false,
|
||||
errorCode: 'VALIDATION_ERROR',
|
||||
message: `Credential validation failed: ${err.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export async function deleteAdFiles(cookie, dirFid, keywords) {
|
||||
const toKeep = [];
|
||||
const extensions = getSusExtensions();
|
||||
for (const file of files) {
|
||||
const ext = file.file.split(".").pop()?.toLowerCase() || "";
|
||||
const ext = file.file_name.split(".").pop()?.toLowerCase() || "";
|
||||
const isSusExt = extensions.includes(ext);
|
||||
if (containsAdKeyword(file.file_name, keywords) || isSusExt) {
|
||||
toDelete.push(file.fid);
|
||||
|
||||
@@ -134,7 +134,7 @@ export function magicRenameDir(dirName) {
|
||||
const noiseCount = Math.random() < 0.3 ? (Math.random() < 0.5 ? 1 : 2) : 0;
|
||||
for (let n = 0; n < noiseCount; n++) {
|
||||
const pos = Math.floor(Math.random() * (baseName.length + 1));
|
||||
const ink = NOISE_CJK[Math.floor(Math.random() * NOISE.length)];
|
||||
const ink = NOISE_CJK[Math.floor(Math.random() * NOISE_CJK.length)];
|
||||
baseName = baseName.slice(0, pos) + ink + baseName.slice(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
||||
*
|
||||
* Flow: token → detail → save → wait_task → rename → share
|
||||
*/
|
||||
export async function saveFromShare(cookie, nickname, shareUrl, sourceTitle) {
|
||||
export async function saveFromShare(cookie, nickname, shareUrl, sourceTitle, retrySave = false) {
|
||||
try {
|
||||
// Parse share token from URL
|
||||
const urlObj = new URL(shareUrl);
|
||||
@@ -69,8 +69,10 @@ export async function saveFromShare(cookie, nickname, shareUrl, sourceTitle) {
|
||||
const fidTokens = topFiles.map(f => f.share_fid_token);
|
||||
// 按日期创建/查找文件夹,每天的转存存入当天文件夹
|
||||
await quark_api.humanDelay();
|
||||
const saveDirName = quark_api.dailyFolderName();
|
||||
console.log(`[Quark] saveFromShare: looking for/create dir "${saveDirName}"`);
|
||||
const saveDirName = retrySave
|
||||
? quark_api.dailyFolderName() + '_' + Math.random().toString(36).slice(2, 6)
|
||||
: quark_api.dailyFolderName();
|
||||
console.log(`[Quark] saveFromShare: looking for/create dir "${saveDirName}"${retrySave ? ' (retry)' : ''}`);
|
||||
const saveDirFid = await findOrCreateDir(cookie, saveDirName);
|
||||
const targetPdirFid = saveDirFid || '0';
|
||||
if (saveDirFid) {
|
||||
|
||||
@@ -98,8 +98,8 @@ export class QuarkDriver {
|
||||
}
|
||||
|
||||
// ==================== Storage (Save from Share) ====================
|
||||
async saveFromShare(shareUrl: string, sourceTitle?: string): Promise<any> {
|
||||
return saveFromShare(this.cookie, this.config.nickname || '', shareUrl, sourceTitle || '');
|
||||
async saveFromShare(shareUrl: string, sourceTitle?: string, retrySave?: boolean): Promise<any> {
|
||||
return saveFromShare(this.cookie, this.config.nickname || '', shareUrl, sourceTitle || '', retrySave);
|
||||
}
|
||||
|
||||
async createDir(dirName: string): Promise<any> {
|
||||
|
||||
@@ -315,6 +315,9 @@ function seedSystemConfigs(db: Database.Database): void {
|
||||
{ key: 'save_reuse_enabled', value: 'true', description: '启用分享链接复用(相同原始链接不再重复转存,直接复用之前的分享链接)' },
|
||||
{ key: 'cleanup_last_run', value: '', description: '上次自动清理时间' },
|
||||
{ key: 'cleanup_last_stats', value: '', description: '上次清理结果统计(JSON)' },
|
||||
{ key: 'search_all_channels', value: 'false', description: '使用所有频道参与搜索(包含未启用频道)' },
|
||||
{ key: 'ip_geo_provider', value: 'apihz', description: 'IP 归属地查询接口提供商' },
|
||||
{ key: 'auto_update_enabled', value: 'false', description: '自动更新镜像(预留,暂未实现)' },
|
||||
];
|
||||
const insert = db.prepare(
|
||||
'INSERT OR IGNORE INTO system_configs (key, value, description) VALUES (?, ?, ?)'
|
||||
|
||||
@@ -161,20 +161,14 @@ export class LinkValidator {
|
||||
return { url, status: 'valid', cloudType, checkedAt, message: summary };
|
||||
}
|
||||
|
||||
// 2. 自定义确认关键词(用户配置的"有效"信号)
|
||||
const validKeywords = loadCustomKeywords('link_valid_keywords');
|
||||
if (validKeywords.some(kw => summary.includes(kw))) {
|
||||
return { url, status: 'valid', cloudType, checkedAt, message: summary };
|
||||
}
|
||||
|
||||
// 3. 自定义失效关键词(用户配置的"失效"信号)
|
||||
const invalidKeywords = loadCustomKeywords('link_invalid_keywords');
|
||||
if (invalidKeywords.some(kw => summary.includes(kw))) {
|
||||
return { url, status: 'invalid', cloudType, checkedAt, message: summary };
|
||||
}
|
||||
|
||||
// 4. 其余全部返回 unknown
|
||||
return { url, status: 'unknown', cloudType, checkedAt, message: summary || '盘搜无法确认' };
|
||||
// 4. 其余全部返回 valid(无失效关键词命中则有效)
|
||||
return { url, status: 'valid', cloudType, checkedAt, message: summary || '盘搜验证通过' };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user