dsh-bili-scroll 0.1.7 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTORS.md +18 -0
- package/LICENSE +21 -31
- package/LICENSE.bak-20261020 +31 -0
- package/NOTICE.md +18 -0
- package/README.md +39 -18
- package/client.js +343 -84
- package/index.js +1298 -93
- package/lib/asr.py +3 -0
- package/lib/dm.mjs +101 -0
- package/lib/favops.py +84 -0
- package/lib/login.mjs +19 -0
- package/lib/state.mjs +39 -8
- package/lib/uid.mjs +61 -0
- package/lib/verify.mjs +1 -1
- package/package.json +4 -2
- package/prompts/duty.md +4 -1
- package/vendor/bilibili-mcp/mcp_server.py +36 -312
package/client.js
CHANGED
|
@@ -49,9 +49,11 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
|
|
50
50
|
const CSS = `
|
|
51
51
|
[data-bd-page]{display:flex;flex-direction:column;gap:18px;padding:2px 2px 8px;max-width:640px;}
|
|
52
|
+
[data-bd-page][data-bd-wide]{max-width:none;width:100%;}
|
|
53
|
+
[data-bd-page][data-bd-wide] [data-bd-list]{grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;}
|
|
52
54
|
[data-bd-sec]{display:flex;flex-direction:column;gap:8px;}
|
|
53
55
|
[data-bd-h]{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-tertiary,#9aa0a6);}
|
|
54
|
-
[data-bd-row]{display:flex;align-items:center;gap:10px;}
|
|
56
|
+
[data-bd-row]{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
|
|
55
57
|
[data-bd-grow]{flex:1;min-width:0;}
|
|
56
58
|
[data-bd-label]{font-size:13px;}
|
|
57
59
|
[data-bd-note]{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a6);}
|
|
@@ -62,31 +64,69 @@ window.__ModuleLoader__.load({
|
|
|
62
64
|
transition:left .15s;display:block;}
|
|
63
65
|
[data-bd-sw][data-on] i{left:19px;}
|
|
64
66
|
[data-bd-sw][disabled]{opacity:.45;cursor:default;}
|
|
65
|
-
[data-bd-sub]{margin-left:14px;padding-left:12px;border-left:2px solid var(--dsw-alias-
|
|
67
|
+
[data-bd-sub]{margin-left:14px;padding-left:12px;border-left:2px solid var(--dsw-alias-bg-layer-3,rgba(255,255,255,.1));
|
|
66
68
|
display:flex;flex-direction:column;gap:10px;}
|
|
67
69
|
[data-bd-input]{width:100%;box-sizing:border-box;padding:7px 9px;border-radius:8px;font-size:12px;
|
|
68
|
-
border:1px solid var(--dsw-alias-
|
|
69
|
-
background:var(--dsw-alias-bg-
|
|
70
|
+
border:1px solid var(--dsw-alias-bg-layer-3,rgba(255,255,255,.14));
|
|
71
|
+
background:var(--dsw-alias-bg-layer-4,rgba(128,128,128,.12));color:inherit;font-family:inherit;}
|
|
70
72
|
[data-bd-input]:focus{outline:none;border-color:#3b82f6;}
|
|
71
73
|
[data-bd-num]{width:88px;text-align:center;}
|
|
72
|
-
[data-bd-btn]{padding:7px 12px;border-radius:8px;font-size:12px;cursor:pointer;font-family:inherit;
|
|
73
|
-
border:1px solid var(--dsw-alias-
|
|
74
|
-
background:var(--dsw-alias-bg-
|
|
74
|
+
[data-bd-btn]{padding:7px 12px;border-radius:8px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;
|
|
75
|
+
border:1px solid var(--dsw-alias-bg-layer-3,rgba(255,255,255,.16));
|
|
76
|
+
background:var(--dsw-alias-bg-layer-4,rgba(128,128,128,.14));color:inherit;}
|
|
75
77
|
[data-bd-btn]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.24));}
|
|
76
78
|
[data-bd-btn][data-primary]{background:#3b82f6;border-color:#3b82f6;color:#fff;}
|
|
77
79
|
[data-bd-btn][disabled]{opacity:.5;cursor:default;}
|
|
78
80
|
[data-bd-msg]{font-size:11px;line-height:16px;padding:7px 9px;border-radius:8px;word-break:break-all;
|
|
79
|
-
background:var(--dsw-alias-bg-
|
|
81
|
+
background:var(--dsw-alias-bg-layer-4,rgba(128,128,128,.12));color:var(--dsw-alias-label-tertiary,#9aa0a6);}
|
|
80
82
|
[data-bd-msg][data-warn]{color:#f59e0b;}
|
|
81
83
|
[data-bd-msg][data-good]{color:#22c55e;}
|
|
82
84
|
[data-bd-qr]{display:flex;gap:14px;align-items:flex-start;}
|
|
83
85
|
[data-bd-qr] img{width:150px;height:150px;border-radius:8px;background:#fff;padding:6px;}
|
|
84
86
|
[data-bd-pre]{margin:6px 0 0;padding:8px 9px;border-radius:8px;max-height:170px;overflow:auto;font-size:11px;
|
|
85
87
|
line-height:15px;white-space:pre-wrap;word-break:break-all;
|
|
86
|
-
background:var(--dsw-alias-bg-
|
|
88
|
+
background:var(--dsw-alias-bg-layer-4,rgba(0,0,0,.25));color:var(--dsw-alias-label-secondary,#c9ccd1);}
|
|
89
|
+
/* ⭐ 2026-10-21 重做:卡片网格 + 颜色全部取「真实存在」的主题变量
|
|
90
|
+
(之前用了 --dsw-alias-bg-layer-1 / --dsw-alias-bg-layer-3 —— 这两个在应用里根本不存在 ⇒
|
|
91
|
+
fallback 落到硬编码深色,而文字用的 --dsw-alias-label-primary 是存在的 ⇒ 浅色主题下变近黑 ⇒ 黑底黑字看不见) */
|
|
92
|
+
[data-bd-list]{margin:10px 0 0;max-height:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;}
|
|
93
|
+
[data-bd-item]{display:flex;flex-direction:column;overflow:hidden;border-radius:12px;
|
|
94
|
+
background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.06));
|
|
95
|
+
border:1px solid var(--dsw-alias-bg-layer-3,rgba(127,127,127,.22));
|
|
96
|
+
color:var(--dsw-alias-label-primary,inherit);}
|
|
97
|
+
[data-bd-cover]{display:block;width:100%;aspect-ratio:16/9;height:auto;object-fit:cover;
|
|
98
|
+
background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.1));}
|
|
99
|
+
[data-bd-item-body]{display:flex;flex-direction:column;gap:4px;padding:9px 11px;min-width:0;}
|
|
100
|
+
[data-bd-item-t]{font-size:13px;line-height:18px;font-weight:600;
|
|
101
|
+
color:var(--dsw-alias-label-primary,inherit);
|
|
102
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word;}
|
|
103
|
+
[data-bd-item-t] a{color:inherit;text-decoration:none;}
|
|
104
|
+
[data-bd-item-t] a:hover{text-decoration:underline;}
|
|
105
|
+
[data-bd-item-n]{font-size:11px;line-height:15px;color:var(--dsw-alias-label-tertiary,inherit);margin:0;}
|
|
106
|
+
[data-bd-item-why]{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary,inherit);
|
|
107
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
|
108
|
+
[data-bd-clickable]{cursor:pointer;transition:border-color .12s,background .12s;}
|
|
109
|
+
[data-bd-clickable]:hover{border-color:var(--dsw-alias-brand-primary,rgba(76,141,255,.5));
|
|
110
|
+
background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1));}
|
|
111
|
+
[data-bd-tabs]{display:flex;gap:4px;padding:4px;border-radius:10px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.08));}
|
|
112
|
+
[data-bd-tab]{flex:1;min-width:0;cursor:pointer;font:inherit;font-size:13px;padding:7px 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,inherit);}
|
|
113
|
+
[data-bd-tab]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1));}
|
|
114
|
+
[data-bd-tab][aria-selected=true]{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,inherit);font-weight:600;box-shadow:0 1px 2px rgba(0,0,0,.12);}
|
|
115
|
+
[data-bd-away]{display:none !important;}
|
|
116
|
+
[data-bd-chip]{cursor:pointer;white-space:nowrap;font:inherit;font-size:12px;padding:3px 10px;border-radius:999px;
|
|
117
|
+
border:1px solid var(--dsw-alias-bg-layer-3,rgba(127,127,127,.22));background:transparent;
|
|
118
|
+
color:var(--dsw-alias-label-secondary,inherit);}
|
|
119
|
+
[data-bd-chip]:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1));}
|
|
120
|
+
[data-bd-chip][aria-selected=true]{color:var(--dsw-alias-label-primary,inherit);
|
|
121
|
+
border-color:var(--dsw-alias-brand-primary,rgba(76,141,255,.6));
|
|
122
|
+
background:color-mix(in srgb,var(--dsw-alias-brand-primary,#4c8dff) 16%,transparent);}
|
|
123
|
+
[data-bd-tier]{font-weight:600;color:var(--dsw-alias-label-secondary,inherit);}
|
|
124
|
+
[data-bd-tier='值得看']{color:#3fb950;}
|
|
125
|
+
[data-bd-tier='可以看']{color:#d29922;}
|
|
126
|
+
[data-bd-tier='不值当']{color:var(--dsw-alias-label-tertiary,inherit);}
|
|
87
127
|
[data-bd-kv]{font-size:11px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a6);word-break:break-all;}
|
|
88
128
|
[data-bd-badge]{font-size:11px;padding:2px 7px;border-radius:6px;
|
|
89
|
-
background:var(--dsw-alias-bg-
|
|
129
|
+
background:var(--dsw-alias-bg-layer-4,rgba(128,128,128,.14));color:var(--dsw-alias-label-secondary,#c9ccd1);}
|
|
90
130
|
`
|
|
91
131
|
|
|
92
132
|
// ⚠️ 与 lib/state.mjs 的 WINDOWS 保持一致(官方口径:工作日 9–12、14–18 是高峰,其余都是谷时)
|
|
@@ -117,30 +157,89 @@ window.__ModuleLoader__.load({
|
|
|
117
157
|
|
|
118
158
|
function useStatus(intervalMs) {
|
|
119
159
|
const [st, setSt] = React.useState(null)
|
|
160
|
+
// ⭐ 2026-10-19(用户报"没有开关这一项,只能让 ai 帮我开"):读不到配置时界面只显示"读设置中…",
|
|
161
|
+
// 用户会以为是开关坏了 ✗ ⇒ 数一下连续失败次数,交给界面把话说清楚(版本太旧 / 没重启 / 读不到)
|
|
162
|
+
const [fails, setFails] = React.useState(0)
|
|
120
163
|
React.useEffect(() => {
|
|
121
164
|
let alive = true
|
|
122
165
|
const tick = async () => {
|
|
123
166
|
try {
|
|
124
167
|
const d = await jget('/status')
|
|
125
|
-
if (alive
|
|
126
|
-
|
|
168
|
+
if (!alive) return
|
|
169
|
+
if (d && d.ok) { setSt(d); setFails(0) } else setFails((n) => n + 1)
|
|
170
|
+
} catch { if (alive) setFails((n) => n + 1) }
|
|
127
171
|
}
|
|
128
172
|
tick()
|
|
129
173
|
const t = setInterval(tick, intervalMs || 5000)
|
|
130
174
|
return () => { alive = false; clearInterval(t) }
|
|
131
175
|
}, [intervalMs])
|
|
132
|
-
return [st, setSt]
|
|
176
|
+
return [st, setSt, fails]
|
|
133
177
|
}
|
|
134
178
|
|
|
135
179
|
/* ────────────── 设置页正文 ────────────── */
|
|
136
180
|
|
|
181
|
+
/* ────────────── 会话头那个 tab:它刷过的视频(最小实验版)────────────── */
|
|
182
|
+
function WatchTab() {
|
|
183
|
+
const PAGE = 30
|
|
184
|
+
const onOpenUrl = (u) => { jpost('/open', { what: 'url', u }).catch(() => {}) }
|
|
185
|
+
const [rows, setRows] = React.useState(null)
|
|
186
|
+
const [st, setSt] = React.useState('') // '' | picked | skipped | commented
|
|
187
|
+
const [tier, setTier] = React.useState('') // '' | 值得看 / 可以看 / 不值当
|
|
188
|
+
const [offset, setOffset] = React.useState(0)
|
|
189
|
+
const [total, setTotal] = React.useState(0)
|
|
190
|
+
const load = (s, t, off) => {
|
|
191
|
+
setRows(null)
|
|
192
|
+
const q = `/watched?limit=${PAGE}&offset=${off}` + (s ? '&state=' + s : '') + (t ? '&tier=' + encodeURIComponent(t) : '')
|
|
193
|
+
jget(q).then((r) => {
|
|
194
|
+
if (!r || !r.ok) { setRows([]); return }
|
|
195
|
+
setRows(r.rows || [])
|
|
196
|
+
setTotal(Number(r.total) || 0)
|
|
197
|
+
}).catch(() => setRows([]))
|
|
198
|
+
}
|
|
199
|
+
React.useEffect(() => { load("", "", 0) }, [])
|
|
200
|
+
const pick = (kind, v) => {
|
|
201
|
+
const next = (kind === 'state' ? st : tier) === v ? '' : v
|
|
202
|
+
if (kind === 'state') setSt(next); else setTier(next)
|
|
203
|
+
setOffset(0)
|
|
204
|
+
load(kind === 'state' ? next : st, kind === 'tier' ? next : tier, 0)
|
|
205
|
+
}
|
|
206
|
+
const go = (d) => { const n = Math.max(0, Math.min(offset + d, Math.max(0, total - 1))); setOffset(n); load(st, tier, n) }
|
|
207
|
+
const chip = (label, on, active) => h('button', { 'data-bd-chip': '', 'aria-selected': active ? 'true' : 'false', onClick: on }, label)
|
|
208
|
+
return h('div', { 'data-bd-page': '', 'data-bd-wide': '' },
|
|
209
|
+
h('div', { 'data-bd-row': '' },
|
|
210
|
+
chip('全部', () => pick('state', ''), st === ''),
|
|
211
|
+
chip('挑了', () => pick('state', 'picked'), st === 'picked'),
|
|
212
|
+
chip('跳过', () => pick('state', 'skipped'), st === 'skipped'),
|
|
213
|
+
chip('发给你了', () => pick('state', 'commented'), st === 'commented'),
|
|
214
|
+
h('div', { 'data-bd-note': '' }, rows === null ? '读取中…' : `共 ${total} 条`)),
|
|
215
|
+
h('div', { 'data-bd-row': '' },
|
|
216
|
+
chip('值得看', () => pick('tier', '值得看'), tier === '值得看'),
|
|
217
|
+
chip('可以看', () => pick('tier', '可以看'), tier === '可以看'),
|
|
218
|
+
chip('不值当', () => pick('tier', '不值当'), tier === '不值当')),
|
|
219
|
+
rows && rows.length ? h('div', { 'data-bd-list': '' },
|
|
220
|
+
rows.map((x, i) => h('div', { key: x.day + x.bvid, 'data-bd-item': '', 'data-bd-clickable': '', title: '在浏览器打开', onClick: () => onOpenUrl(x.url) },
|
|
221
|
+
i < 30 ? h('img', { 'data-bd-cover': '', src: API + '/cover?bv=' + x.bvid, loading: 'lazy', alt: '' }) : null,
|
|
222
|
+
h('div', { 'data-bd-item-body': '' },
|
|
223
|
+
h('div', { 'data-bd-item-t': '' }, x.title),
|
|
224
|
+
h('div', { 'data-bd-item-n': '' }, `${x.day} · ${x.up} · `, x.picked ? '挑中了' : '跳过', (x.delivered || x.commented) ? ' · 已发给你' : '',
|
|
225
|
+
x.tier ? h('span', { 'data-bd-tier': x.tier }, ' · ' + x.tier) : null),
|
|
226
|
+
x.why ? h('div', { 'data-bd-item-why': '' }, x.why) : null))))
|
|
227
|
+
: h('div', { 'data-bd-note': '' }, rows === null ? '' : '这一类还没有记录 —— 跑一轮之后就有了'),
|
|
228
|
+
total > PAGE ? h('div', { 'data-bd-row': '' },
|
|
229
|
+
h('button', { 'data-bd-btn': '', disabled: offset <= 0, onClick: () => go(-PAGE) }, '上一页'),
|
|
230
|
+
h('div', { 'data-bd-note': '' }, `${offset + 1}–${Math.min(offset + PAGE, total)} / ${total}`),
|
|
231
|
+
h('button', { 'data-bd-btn': '', disabled: offset + PAGE >= total, onClick: () => go(PAGE) }, '下一页')) : null)
|
|
232
|
+
}
|
|
233
|
+
|
|
137
234
|
function Panel() {
|
|
138
|
-
const [st, setSt] = useStatus(5000)
|
|
235
|
+
const [st, setSt, fails] = useStatus(5000)
|
|
139
236
|
const [busy, setBusy] = React.useState('')
|
|
140
237
|
const [msg, setMsg] = React.useState('')
|
|
141
238
|
const [msgTone, setMsgTone] = React.useState('')
|
|
142
239
|
const [kws, setKws] = React.useState(null)
|
|
143
|
-
const [
|
|
240
|
+
const [favFolders, setFavFolders] = React.useState(null) // 收藏夹列表(下拉用,null = 还没读)
|
|
241
|
+
const [favId, setFavId] = React.useState(null) // 选中的收藏夹 fid(字符串)
|
|
242
|
+
const [persona, setPersona] = React.useState(null) // 人设提示词草稿
|
|
144
243
|
const [rl, setRl] = React.useState(null)
|
|
145
244
|
const [newVer, setNewVer] = React.useState('') // 发现的新版本号(空 = 没发现) // 日报形态:full / brief / off
|
|
146
245
|
// ⚠️ 输入框必须绑"本地草稿":页面每 5 秒轮询 /status,服务端那份会把 config 盖掉
|
|
@@ -149,11 +248,21 @@ window.__ModuleLoader__.load({
|
|
|
149
248
|
const [perRound, setPerRound] = React.useState(null)
|
|
150
249
|
const [replyPer, setReplyPer] = React.useState(null)
|
|
151
250
|
const [dmUid, setDmUid] = React.useState(null) // 私信收件人 UID(本地草稿)
|
|
251
|
+
const [dmWatch, setDmWatch] = React.useState(null) // 私信双向开关
|
|
152
252
|
const [dmMode, setDmMode] = React.useState(null) // 投递方式:off / only / both
|
|
153
253
|
const [mention, setMention] = React.useState(null)
|
|
154
254
|
const [login, setLogin] = React.useState(null)
|
|
155
255
|
const [runNote, setRunNote] = React.useState(null)
|
|
156
256
|
const [report, setReport] = React.useState(null) // 非 null = 报告已展开 // 顶部那行状态(跑一轮/装依赖的反馈原来在最底下,看不见)
|
|
257
|
+
const [watchedRows, setWatchedRows] = React.useState(null) // null = 还没读;[] = 读到了但没记录
|
|
258
|
+
const [watchedPicked, setWatchedPicked] = React.useState(false) // 只看挑中的
|
|
259
|
+
const [watchedState, setWatchedState] = React.useState('')
|
|
260
|
+
const [watchedTier, setWatchedTier] = React.useState('')
|
|
261
|
+
const [watchedOffset, setWatchedOffset] = React.useState(0)
|
|
262
|
+
const [watchedTotal, setWatchedTotal] = React.useState(0)
|
|
263
|
+
const [tab, setTab] = React.useState('daily') // daily | taste | social | adv
|
|
264
|
+
const [diag, setDiag] = React.useState(null) // 诊断信息文本
|
|
265
|
+
const [checks, setChecks] = React.useState(null) // 环境自检结果 [{name, ok, detail, fix}]
|
|
157
266
|
const [loginState, setLoginState] = React.useState('')
|
|
158
267
|
const [loginTone, setLoginTone] = React.useState('')
|
|
159
268
|
/* ⚠️ 扫码轮询的 timer 必须能停:切走设置页后还在跑的话会在已卸载的组件上 setState,
|
|
@@ -168,16 +277,25 @@ window.__ModuleLoader__.load({
|
|
|
168
277
|
const cfg = st?.config
|
|
169
278
|
React.useEffect(() => {
|
|
170
279
|
if (cfg && kws === null) setKws((cfg.keywords || []).join('\n'))
|
|
171
|
-
if (cfg &&
|
|
280
|
+
if (cfg && persona === null) setPersona(cfg.personaPrompt || '')
|
|
172
281
|
if (cfg && rl === null) setRl(cfg.reportLevel || 'full')
|
|
173
282
|
if (cfg && perDay === null) setPerDay(String(cfg.perDay ?? 5))
|
|
174
283
|
if (cfg && perRound === null) setPerRound(String(cfg.commentPerRound ?? 1))
|
|
175
284
|
if (cfg && replyPer === null) setReplyPer(String(cfg.replyPerRound ?? 1))
|
|
176
285
|
if (cfg && mention === null) setMention(cfg.mentionName || '')
|
|
177
286
|
if (cfg && dmUid === null) setDmUid(cfg.dmUid ? String(cfg.dmUid) : '')
|
|
178
|
-
if (cfg && dmMode === null) setDmMode(cfg.dmMode || '
|
|
287
|
+
if (cfg && dmMode === null) setDmMode(cfg.dmMode || 'only')
|
|
288
|
+
if (cfg && dmWatch === null) setDmWatch(cfg.dmWatch !== false) // 2.0 起默认开
|
|
289
|
+
if (cfg && favId === null) setFavId(String(cfg.favFolderId ?? 0))
|
|
179
290
|
}, [cfg])
|
|
180
291
|
|
|
292
|
+
// ⭐ 收藏夹列表:只读一次,读不到就空列表(别每 5 秒重拉)
|
|
293
|
+
React.useEffect(() => {
|
|
294
|
+
if (favFolders !== null) return
|
|
295
|
+
jget('/fav-folders').then((r) => { if (alive.current) setFavFolders(r && r.ok ? (r.folders || []) : []) })
|
|
296
|
+
.catch(() => { if (alive.current) setFavFolders([]) })
|
|
297
|
+
}, [favFolders])
|
|
298
|
+
|
|
181
299
|
const win = { ...DEFAULT_WINDOWS, ...(st?.windows || {}) }
|
|
182
300
|
|
|
183
301
|
const say = (text, tone) => {
|
|
@@ -197,6 +315,28 @@ window.__ModuleLoader__.load({
|
|
|
197
315
|
finally { setBusy('') }
|
|
198
316
|
}
|
|
199
317
|
|
|
318
|
+
// 「它刷过的视频」:点开才读,读一次就留着(不会每 5 秒重拉)
|
|
319
|
+
const WATCH_PAGE = 30
|
|
320
|
+
const onLoadWatched = async (st, tr, off) => {
|
|
321
|
+
setBusy('watched')
|
|
322
|
+
try {
|
|
323
|
+
const q = `/watched?limit=${WATCH_PAGE}&offset=${off}` + (st ? '&state=' + st : '') + (tr ? '&tier=' + encodeURIComponent(tr) : '')
|
|
324
|
+
const r = await jget(q)
|
|
325
|
+
setWatchedRows(r && r.ok ? (r.rows || []) : [])
|
|
326
|
+
setWatchedTotal(r && r.ok ? (Number(r.total) || 0) : 0)
|
|
327
|
+
setWatchedState(st)
|
|
328
|
+
setWatchedTier(tr)
|
|
329
|
+
setWatchedOffset(off)
|
|
330
|
+
if (!r || r.ok === false) say('读不到记录:' + ((r && r.error) || '未知原因'), 'warn')
|
|
331
|
+
} catch (e) { say('读不到记录:' + String(e?.message ?? e), 'warn') } finally { setBusy('') }
|
|
332
|
+
}
|
|
333
|
+
const onWatchPick = (kind, v) => {
|
|
334
|
+
const cur = kind === 'state' ? watchedState : watchedTier
|
|
335
|
+
const next = cur === v ? '' : v
|
|
336
|
+
return onLoadWatched(kind === 'state' ? next : watchedState, kind === 'tier' ? next : watchedTier, 0)
|
|
337
|
+
}
|
|
338
|
+
const onWatchPage = (d) => onLoadWatched(watchedState, watchedTier, Math.max(0, watchedOffset + d))
|
|
339
|
+
|
|
200
340
|
const onShowReport = async () => {
|
|
201
341
|
if (report !== null) { setReport(null); return } // 再点一次收起
|
|
202
342
|
setBusy('report')
|
|
@@ -221,6 +361,25 @@ window.__ModuleLoader__.load({
|
|
|
221
361
|
} catch (e) { say(String(e?.message ?? e), 'warn') } finally { setBusy('') }
|
|
222
362
|
}
|
|
223
363
|
|
|
364
|
+
// ⭐ 把"要日志"变成"点一下"(2026-10-21)
|
|
365
|
+
const onDiagnose = async () => {
|
|
366
|
+
setBusy('diag')
|
|
367
|
+
try {
|
|
368
|
+
const r = await jget('/diagnose')
|
|
369
|
+
const text = String(r?.text || '(没拿到诊断信息)')
|
|
370
|
+
setDiag(text)
|
|
371
|
+
copyText(text).catch(() => {})
|
|
372
|
+
say('诊断信息已生成并复制到剪贴板', 'good')
|
|
373
|
+
} catch (e) { say('生成诊断信息失败:' + String(e?.message ?? e), 'warn') } finally { setBusy('') }
|
|
374
|
+
}
|
|
375
|
+
const onSelfCheck = async () => {
|
|
376
|
+
setBusy('selfcheck')
|
|
377
|
+
try {
|
|
378
|
+
const r = await jget('/selfcheck')
|
|
379
|
+
setChecks(Array.isArray(r?.items) ? r.items : [])
|
|
380
|
+
} catch (e) { say('自检失败:' + String(e?.message ?? e), 'warn') } finally { setBusy('') }
|
|
381
|
+
}
|
|
382
|
+
|
|
224
383
|
const copyText = async (text) => {
|
|
225
384
|
const v = String(text || '')
|
|
226
385
|
try {
|
|
@@ -253,10 +412,11 @@ window.__ModuleLoader__.load({
|
|
|
253
412
|
try {
|
|
254
413
|
const r = await jget('/update')
|
|
255
414
|
if (r && r.ok && r.latest) {
|
|
415
|
+
const from = r.source === 'github' ? '(npm 上还没发上去,这个版本来自 GitHub)' : ''
|
|
256
416
|
if (r.hasNew) {
|
|
257
417
|
setNewVer(r.latest)
|
|
258
|
-
say('有新版 v' + r.latest + '(你的是 v' + r.current + '
|
|
259
|
-
} else say('已经是最新版 v' + r.current, 'good')
|
|
418
|
+
say('有新版 v' + r.latest + '(你的是 v' + r.current + ')' + from + ' —— 点右边的「一键升级」,完了重启一次 dsh', 'warn')
|
|
419
|
+
} else say('已经是最新版 v' + r.current + from, 'good')
|
|
260
420
|
} else {
|
|
261
421
|
say('查不到最新版(可能网络不通)—— 你现在的版本是 v' + ((r && r.current) || '?'))
|
|
262
422
|
}
|
|
@@ -341,13 +501,28 @@ window.__ModuleLoader__.load({
|
|
|
341
501
|
finally { setBusy('') }
|
|
342
502
|
}
|
|
343
503
|
|
|
504
|
+
const onFavToday = async () => {
|
|
505
|
+
setBusy('fav')
|
|
506
|
+
try {
|
|
507
|
+
const r = await jpost('/fav-today', {})
|
|
508
|
+
if (!r || r.ok === false) say('收藏没做成:' + ((r && r.error) || '未知原因'), 'warn')
|
|
509
|
+
else say(`收藏好了:${r.added}/${r.total} 条` + (r.failures && r.failures.length ? `(失败 ${r.failures.length} 条)` : ''), 'good')
|
|
510
|
+
} catch (e) { say(String(e?.message ?? e), 'warn') } finally { setBusy('') }
|
|
511
|
+
}
|
|
512
|
+
|
|
344
513
|
const onSaveText = async () => {
|
|
345
514
|
const list = String(kws || '').split('\n').map((s) => s.trim()).filter(Boolean)
|
|
346
|
-
await patch({ keywords: list,
|
|
515
|
+
await patch({ keywords: list, personaPrompt: String(persona || '').trim() }, '关键词 / 人设 / 工作区')
|
|
347
516
|
}
|
|
348
517
|
|
|
349
518
|
if (!cfg) {
|
|
350
|
-
return h('div', { 'data-bd-page': '' },
|
|
519
|
+
return h('div', { 'data-bd-page': '' },
|
|
520
|
+
h('div', { 'data-bd-note': '' }, fails >= 3
|
|
521
|
+
? '读不到插件配置(已经试了 ' + fails + ' 次)。常见原因有三个:装的还是 0.1.11 / 0.1.12(那两版有 bug,会卡在这里);刚装完没重启 dsh;或者插件加载失败。'
|
|
522
|
+
: '读设置中…'),
|
|
523
|
+
fails >= 6 ? h('div', { 'data-bd-note': '' },
|
|
524
|
+
'可以先试:升级到最新版(dsh plugin --profile web add github:c1overzz/dsh-bili-scroll)→ 重启 dsh;还不行就把 ' +
|
|
525
|
+
'%USERPROFILE%\\.dsh\\bili-duty\\duty.log 的最后几十行发给作者。') : null)
|
|
351
526
|
}
|
|
352
527
|
|
|
353
528
|
const sw = (k) => Boolean(cfg[k])
|
|
@@ -365,8 +540,17 @@ window.__ModuleLoader__.load({
|
|
|
365
540
|
runNote ? h('div', { 'data-bd-msg': '', 'data-good': runNote.tone === 'good' ? '' : undefined,
|
|
366
541
|
'data-warn': runNote.tone === 'warn' ? '' : undefined }, runNote.t) : null,
|
|
367
542
|
|
|
543
|
+
|
|
368
544
|
/* ── 定时 ── */
|
|
369
|
-
h('div', { 'data-bd-
|
|
545
|
+
h('div', { 'data-bd-tabs': '' },
|
|
546
|
+
[['daily', '日常'], ['taste', '偏好'], ['social', '互动'], ['adv', '高级']].map(([v, label]) =>
|
|
547
|
+
h('button', { key: v, 'data-bd-tab': '', 'aria-selected': tab === v ? 'true' : 'false', onClick: () => setTab(v) }, label))),
|
|
548
|
+
|
|
549
|
+
h('div', { 'data-bd-row': '' },
|
|
550
|
+
h('button', { 'data-bd-btn': '', 'data-primary': '', disabled: Boolean(busy), onClick: onSaveText }, '保存'),
|
|
551
|
+
msg ? h('div', { 'data-bd-msg': '', 'data-good': msgTone === 'good' ? '' : undefined, 'data-warn': msgTone === 'warn' ? '' : undefined }, msg) : null),
|
|
552
|
+
|
|
553
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'daily', 'data-bd-away': tab === 'daily' ? undefined : '' },
|
|
370
554
|
h('div', { 'data-bd-h': '' }, '什么时候自己跑'),
|
|
371
555
|
h(ToggleRow, {
|
|
372
556
|
label: '峰谷时段自动刷',
|
|
@@ -384,7 +568,7 @@ window.__ModuleLoader__.load({
|
|
|
384
568
|
}))) : null),
|
|
385
569
|
|
|
386
570
|
/* ── 一轮挑几条 ── */
|
|
387
|
-
h('div', { 'data-bd-sec': '' },
|
|
571
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'daily', 'data-bd-away': tab === 'daily' ? undefined : '' },
|
|
388
572
|
h('div', { 'data-bd-h': '' }, '一轮挑几条'),
|
|
389
573
|
h('div', { 'data-bd-row': '' },
|
|
390
574
|
h('div', { 'data-bd-grow': '' },
|
|
@@ -399,15 +583,51 @@ window.__ModuleLoader__.load({
|
|
|
399
583
|
st.lastRunAt ? h('div', { 'data-bd-note': '' }, `上次跑:${new Date(st.lastRunAt).toLocaleString('zh-CN')}`) : null),
|
|
400
584
|
|
|
401
585
|
/* ── 发言与转写 ── */
|
|
402
|
-
h('div', { 'data-bd-sec': '' },
|
|
403
|
-
h('div', { 'data-bd-h': '' }, '
|
|
404
|
-
h(
|
|
586
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'social', 'data-bd-away': tab === 'social' ? undefined : '' },
|
|
587
|
+
h('div', { 'data-bd-h': '' }, '它怎么把结果给你'),
|
|
588
|
+
h('div', { 'data-bd-label': '' }, '投递方式'),
|
|
589
|
+
h('select', {
|
|
590
|
+
'data-bd-input': '', value: dmMode ?? 'only',
|
|
591
|
+
onChange: (e) => { setDmMode(e.target.value); patch({ dmMode: e.target.value }, '投递方式') },
|
|
592
|
+
},
|
|
593
|
+
h('option', { value: 'only' }, '只发私信 —— 一条公开评论都不发(默认,最不打扰别人)'),
|
|
594
|
+
h('option', { value: 'off' }, '只发评论 —— 在视频下面 @ 你(公开,别人也看得见)'),
|
|
595
|
+
h('option', { value: 'both' }, '私信 + 评论 —— 两边都发')),
|
|
596
|
+
h('div', { 'data-bd-label': '' }, '发给谁 / @ 谁(B站 昵称)'),
|
|
597
|
+
h('input', {
|
|
598
|
+
'data-bd-input': '', value: mention ?? '',
|
|
599
|
+
placeholder: '填你自己的 B站 昵称;私信按它查 UID,评论也 @ 它',
|
|
600
|
+
onChange: (e) => setMention(e.target.value),
|
|
601
|
+
onBlur: () => { const v = String(mention || '').trim(); setMention(v); patch({ mentionName: v }, '发给谁') },
|
|
602
|
+
}),
|
|
603
|
+
(dmMode && dmMode !== 'off') ? h('div', { 'data-bd-note': '' }, '私信会发给他(用昵称自动查一次 UID);查不到就改回只发评论,不会一条都不送。') : null,
|
|
604
|
+
(dmMode && dmMode !== 'off') ? h('div', { 'data-bd-note': '' }, '私信内容 = 本轮每条的「标题 + 链接 + 一句话」,一轮合并成一条发。⚠️ 建议先让这个号和收件人互关:B站 对「陌生人私信」会限流,还可能不弹提醒。') : null,
|
|
605
|
+
|
|
606
|
+
(dmMode === 'off' || dmMode === 'both') ? h('div', { 'data-bd-sep': '' }) : null,
|
|
607
|
+
(dmMode === 'off' || dmMode === 'both') ? h(ToggleRow, {
|
|
608
|
+
label: '自动评论并 @ 你',
|
|
609
|
+
note: '开了之后它会在挑中的视频下面留评论,并 @ 上面那个人',
|
|
610
|
+
on: sw('autoComment'),
|
|
611
|
+
onChange: (v) => patch({ autoComment: v }, '自动评论'),
|
|
612
|
+
}) : null,
|
|
613
|
+
(dmMode === 'off' || dmMode === 'both') && sw('autoComment') ? h('div', { 'data-bd-sub': '' },
|
|
614
|
+
h('div', { 'data-bd-row': '' },
|
|
615
|
+
h('div', { 'data-bd-grow': '' },
|
|
616
|
+
h('div', { 'data-bd-label': '' }, '每轮最多发几条评论'),
|
|
617
|
+
h('div', { 'data-bd-note': '' }, '每条评论里 @ 一次上面那个人;填 0 = 这一轮不发评论')),
|
|
618
|
+
h('input', {
|
|
619
|
+
'data-bd-input': '', 'data-bd-num': '', type: 'number', min: 0, max: 20,
|
|
620
|
+
value: perRound ?? '',
|
|
621
|
+
onChange: (e) => setPerRound(e.target.value),
|
|
622
|
+
onBlur: () => { const n = Math.max(0, Math.min(Number(perRound) || 0, 20)); setPerRound(String(n)); patch({ commentPerRound: n }, '每轮发几条评论') },
|
|
623
|
+
}))) : null,
|
|
624
|
+
(dmMode === 'off' || dmMode === 'both') ? h(ToggleRow, {
|
|
405
625
|
label: '回复评论(只回楼中楼)',
|
|
406
|
-
note: '
|
|
626
|
+
note: '开了之后它只回「直接回复到你那条评论下面的人」,不参与讨论',
|
|
407
627
|
on: sw('replyToComments'),
|
|
408
628
|
onChange: (v) => patch({ replyToComments: v }, '回复评论'),
|
|
409
|
-
}),
|
|
410
|
-
sw('replyToComments') ? h('div', { 'data-bd-sub': '' },
|
|
629
|
+
}) : null,
|
|
630
|
+
(dmMode === 'off' || dmMode === 'both') && sw('replyToComments') ? h('div', { 'data-bd-sub': '' },
|
|
411
631
|
h('div', { 'data-bd-row': '' },
|
|
412
632
|
h('div', { 'data-bd-grow': '' },
|
|
413
633
|
h('div', { 'data-bd-label': '' }, '每轮最多回几条'),
|
|
@@ -416,52 +636,23 @@ window.__ModuleLoader__.load({
|
|
|
416
636
|
'data-bd-input': '', 'data-bd-num': '', type: 'number', min: 1, max: 10,
|
|
417
637
|
value: replyPer ?? '',
|
|
418
638
|
onChange: (e) => setReplyPer(e.target.value),
|
|
419
|
-
onBlur: () => { const n = Math.max(1, Math.min(Number(replyPer) || 1, 10)); setReplyPer(String(n)); patch({ replyPerRound: n }, '
|
|
639
|
+
onBlur: () => { const n = Math.max(1, Math.min(Number(replyPer) || 1, 10)); setReplyPer(String(n)); patch({ replyPerRound: n }, '每轮回几条') },
|
|
420
640
|
}))) : null,
|
|
641
|
+
|
|
642
|
+
h('div', { 'data-bd-sep': '' }),
|
|
421
643
|
h(ToggleRow, {
|
|
422
|
-
label: '
|
|
423
|
-
note: '
|
|
424
|
-
on:
|
|
425
|
-
onChange: (v) => patch({
|
|
644
|
+
label: '私信双向(有人私信它就回一句)',
|
|
645
|
+
note: '默认开。你在 B站 私信里跟它说话,它会回你(陌生人的私信不回)。⭐ 查私信本身不花 token,只有真收到你的新消息时才花一点点。',
|
|
646
|
+
on: Boolean(dmWatch),
|
|
647
|
+
onChange: (v) => { setDmWatch(v); patch({ dmWatch: v }, '私信双向') },
|
|
426
648
|
}),
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
value: perRound ?? '',
|
|
435
|
-
onChange: (e) => setPerRound(e.target.value),
|
|
436
|
-
// ⭐ 0 是合法值(= 这一轮不发评论);范围与 index.js 的 clampPatch 一致(0–20)
|
|
437
|
-
onBlur: () => { const n = Math.max(0, Math.min(Number(perRound) || 0, 20)); setPerRound(String(n)); patch({ commentPerRound: n }, '每轮几条评论') },
|
|
438
|
-
})),
|
|
439
|
-
h('div', { 'data-bd-label': '' }, '要 @ 谁(B站 昵称)'),
|
|
440
|
-
h('input', {
|
|
441
|
-
'data-bd-input': '', value: mention ?? '',
|
|
442
|
-
placeholder: '填你要 @ 的那个人的 B站 昵称;留空 = 只写报告、不发评论',
|
|
443
|
-
onChange: (e) => setMention(e.target.value),
|
|
444
|
-
onBlur: () => { const v = String(mention || '').trim(); setMention(v); patch({ mentionName: v }, '要 @ 谁') },
|
|
445
|
-
}),
|
|
446
|
-
h('div', { 'data-bd-note': '' }, '留空的话它就不发评论了 —— 因为评论存在的意义就是 @ 那一下')) : null,
|
|
447
|
-
h('div', { 'data-bd-label': '' }, '投递方式(评论 还是 私信)'),
|
|
448
|
-
h('select', {
|
|
449
|
-
'data-bd-input': '', value: dmMode ?? 'off',
|
|
450
|
-
onChange: (e) => { setDmMode(e.target.value); patch({ dmMode: e.target.value }, '投递方式') },
|
|
451
|
-
},
|
|
452
|
-
h('option', { value: 'off' }, '只发评论 —— 在视频下面 @ 你(默认)'),
|
|
453
|
-
h('option', { value: 'only' }, '只私信 —— 一条公开评论都不发,最不打扰别人'),
|
|
454
|
-
h('option', { value: 'both' }, '评论 + 私信 —— 两边都发')),
|
|
455
|
-
(dmMode && dmMode !== 'off') ? h('div', { 'data-bd-sub': '' },
|
|
456
|
-
h('div', { 'data-bd-label': '' }, '私信发给谁(B站 UID,数字)'),
|
|
457
|
-
h('input', {
|
|
458
|
-
'data-bd-input': '', 'data-bd-num': '', type: 'number', min: 0, value: dmUid ?? '',
|
|
459
|
-
placeholder: '对方的数字 UID;比如你自己的另一个号',
|
|
460
|
-
onChange: (e) => setDmUid(e.target.value),
|
|
461
|
-
onBlur: () => { const n = Math.max(0, Number(dmUid) || 0); setDmUid(n ? String(n) : ''); patch({ dmUid: n }, '私信收件人') },
|
|
462
|
-
}),
|
|
463
|
-
h('div', { 'data-bd-note': '' }, '私信内容 = 本轮每条的「标题 + 链接 + 一句话」,一轮合并成一条发(不受 150 字限制)'),
|
|
464
|
-
h('div', { 'data-bd-note': '' }, '⚠️ 建议先让这个号和收件人互关:B站 对「陌生人私信」会限流,还可能不弹提醒')) : null,
|
|
649
|
+
// ⭐ 2026-10-23:**不加新输入框**,只把"它认谁当主人"显示出来 —— 填错当场看得见 ✓
|
|
650
|
+
(st.ownerInfo && dmWatch) ? h('div', { 'data-bd-note': '' },
|
|
651
|
+
'它会认这个人当主人:',
|
|
652
|
+
(st.ownerInfo.name ? st.ownerInfo.name + ' · ' : ''),
|
|
653
|
+
'uid ' + st.ownerInfo.uid,
|
|
654
|
+
st.ownerInfo.self ? '(就是现在登录的这个号)' : '',
|
|
655
|
+
' —— 想换人,改上面那个「要 @ 谁」就行') : null,
|
|
465
656
|
h(ToggleRow, {
|
|
466
657
|
label: '本地转写没字幕的视频',
|
|
467
658
|
note: '默认关;开了以后,没字幕的视频会在本机转写一遍(不花 API 钱)',
|
|
@@ -484,22 +675,22 @@ window.__ModuleLoader__.load({
|
|
|
484
675
|
h('pre', { 'data-bd-pre': '' }, st.asrTail)) : null) : null),
|
|
485
676
|
|
|
486
677
|
/* ── 关注什么 / 在哪儿刷 ── */
|
|
487
|
-
h('div', { 'data-bd-sec': '' },
|
|
678
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'taste', 'data-bd-away': tab === 'taste' ? undefined : '' },
|
|
488
679
|
h('div', { 'data-bd-h': '' }, '关注什么'),
|
|
489
680
|
h('textarea', {
|
|
490
681
|
'data-bd-input': '', rows: 5, placeholder: '一行一个关键词;留空用内置那几组',
|
|
491
682
|
value: kws ?? '', onChange: (e) => setKws(e.target.value),
|
|
492
683
|
}),
|
|
493
|
-
h('div', { 'data-bd-label': '' }, '
|
|
494
|
-
h('
|
|
495
|
-
'data-bd-input': '',
|
|
496
|
-
placeholder: '
|
|
497
|
-
onChange: (e) =>
|
|
684
|
+
h('div', { 'data-bd-label': '' }, '它按什么说话(人设提示词,可留空)'),
|
|
685
|
+
h('textarea', {
|
|
686
|
+
'data-bd-input': '', rows: 6,
|
|
687
|
+
placeholder: '想让它怎么说话就写在这儿 —— 比如「叫我某某,说话简短点,别用 emoji」。也可以直接填一个文件路径(如 D:\\deepseek\\AGENTS.md,或它所在的那个目录),每次运行都会重新读那个文件。留空 = 按中性说话(不扮演任何人)。',
|
|
688
|
+
value: persona ?? '', onChange: (e) => setPersona(e.target.value),
|
|
498
689
|
}),
|
|
499
690
|
h('div', { 'data-bd-note': '' },
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
691
|
+
(st.personaFromWs && String(persona || '').trim() === '')
|
|
692
|
+
? '你现在还是用「工作区」里那份 AGENTS.md 说话的(照旧生效)。想把它搬到这儿改,就把上面的路径填成本机那个目录。'
|
|
693
|
+
: '改完点下面「保存」;下一轮就按这个说话。'),
|
|
503
694
|
h('div', { 'data-bd-label': '' }, '日报写多细'),
|
|
504
695
|
h('select', {
|
|
505
696
|
'data-bd-input': '', value: rl ?? 'full',
|
|
@@ -508,12 +699,66 @@ window.__ModuleLoader__.load({
|
|
|
508
699
|
h('option', { value: 'full' }, '完整 —— 每条:标题 / UP / 为什么值得(默认)'),
|
|
509
700
|
h('option', { value: 'brief' }, '薄 —— 每条一两行,只留值不值得 + 链接'),
|
|
510
701
|
h('option', { value: 'off' }, '不写 —— 只发评论,不生成日报文件')),
|
|
702
|
+
h('div', { 'data-bd-label': '' }, '自动收藏到(这一轮挑中的视频存进你的收藏夹)'),
|
|
703
|
+
h('select', {
|
|
704
|
+
'data-bd-input': '', value: favId ?? '0',
|
|
705
|
+
onChange: (e) => { setFavId(e.target.value); patch({ favFolderId: Math.max(0, Number(e.target.value) || 0) }, '自动收藏') },
|
|
706
|
+
},
|
|
707
|
+
h('option', { value: '0' }, '不收藏(默认)'),
|
|
708
|
+
(favFolders || []).map((f) => h('option', { key: String(f.id), value: String(f.id) }, `${f.title}(${f.count} 条)`))),
|
|
709
|
+
h('div', { 'data-bd-row': '' },
|
|
710
|
+
h('button', { 'data-bd-btn': '', disabled: busy === 'fav', onClick: onFavToday }, '补收藏今天的'),
|
|
711
|
+
h('div', { 'data-bd-note': '' },
|
|
712
|
+
favFolders === null ? '收藏夹列表读取中…'
|
|
713
|
+
: (favFolders.length ? '每轮跑完会把挑中的视频存进上面选的收藏夹'
|
|
714
|
+
: '读不到收藏夹 —— 先「扫码登录」,或过会儿重开设置页'))),
|
|
715
|
+
|
|
511
716
|
h('div', { 'data-bd-row': '' },
|
|
512
|
-
h('button', { 'data-bd-btn': '', 'data-primary': '', disabled: Boolean(busy), onClick: onSaveText }, '保存'),
|
|
513
717
|
msg ? h('div', { 'data-bd-msg': '', 'data-good': msgTone === 'good' ? '' : undefined, 'data-warn': msgTone === 'warn' ? '' : undefined }, msg) : null)),
|
|
514
718
|
|
|
515
719
|
/* ── 环境与账号 ── */
|
|
516
|
-
|
|
720
|
+
/* ── 它刷过的视频 ── */
|
|
721
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'daily', 'data-bd-away': tab === 'daily' ? undefined : '' },
|
|
722
|
+
h('div', { 'data-bd-h': '' }, '它刷过的视频'),
|
|
723
|
+
h('div', { 'data-bd-row': '' },
|
|
724
|
+
h('button', { 'data-bd-btn': '', disabled: busy === 'watched', onClick: () => (watchedRows !== null ? setWatchedRows(null) : onLoadWatched(watchedState, watchedTier, watchedOffset)) },
|
|
725
|
+
watchedRows !== null ? '收起' : '看它刷过什么'),
|
|
726
|
+
h('div', { 'data-bd-note': '' },
|
|
727
|
+
watchedRows === null ? '(点一下读出来;数据来自判重表 seen.md + 报告里的档位)'
|
|
728
|
+
: (watchedTotal ? `共 ${watchedTotal} 条` : '这一类还没有记录 —— 跑一轮之后就有了'))),
|
|
729
|
+
watchedRows !== null ? h('div', { 'data-bd-row': '' },
|
|
730
|
+
[['', '全部'], ['picked', '挑了'], ['skipped', '跳过'], ['commented', '发给你了']].map(([v, label]) =>
|
|
731
|
+
h('button', { key: v || 'all', 'data-bd-chip': '', 'aria-selected': watchedState === v ? 'true' : 'false', onClick: () => onWatchPick('state', v) }, label)),
|
|
732
|
+
['值得看', '可以看', '不值当'].map((v) =>
|
|
733
|
+
h('button', { key: v, 'data-bd-chip': '', 'aria-selected': watchedTier === v ? 'true' : 'false', onClick: () => onWatchPick('tier', v) }, v))) : null,
|
|
734
|
+
watchedRows !== null && watchedRows.length ? h('div', { 'data-bd-list': '' },
|
|
735
|
+
watchedRows.map((x, i) => h('div', { key: x.day + x.bvid, 'data-bd-item': '' },
|
|
736
|
+
// 封面走宿主代理(直连 i0.hdslb.com 会被防盗链挡,也等于把使用者 IP 交出去)
|
|
737
|
+
i < 40 ? h('img', { 'data-bd-cover': '', src: API + '/cover?bv=' + x.bvid, loading: 'lazy', alt: '' }) : null,
|
|
738
|
+
h('div', { 'data-bd-item-body': '' },
|
|
739
|
+
h('div', { 'data-bd-item-t': '' },
|
|
740
|
+
h('a', { href: x.url, target: '_blank', rel: 'noreferrer' }, x.title)),
|
|
741
|
+
h('div', { 'data-bd-item-n': '' },
|
|
742
|
+
`${x.day} · ${x.up} · `, x.picked ? '挑中了' : '跳过', (x.delivered || x.commented) ? ' · 已发给你' : '',
|
|
743
|
+
x.tier ? h('span', { 'data-bd-tier': x.tier }, ' · ' + x.tier) : null),
|
|
744
|
+
x.why ? h('div', { 'data-bd-item-why': '' }, x.why) : null))))
|
|
745
|
+
: null,
|
|
746
|
+
watchedRows !== null && watchedTotal > WATCH_PAGE ? h('div', { 'data-bd-row': '' },
|
|
747
|
+
h('button', { 'data-bd-btn': '', disabled: watchedOffset <= 0, onClick: () => onWatchPage(-WATCH_PAGE) }, '上一页'),
|
|
748
|
+
h('div', { 'data-bd-note': '' }, `${watchedOffset + 1}–${Math.min(watchedOffset + WATCH_PAGE, watchedTotal)} / ${watchedTotal}`),
|
|
749
|
+
h('button', { 'data-bd-btn': '', disabled: watchedOffset + WATCH_PAGE >= watchedTotal, onClick: () => onWatchPage(WATCH_PAGE) }, '下一页')) : null),
|
|
750
|
+
|
|
751
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'adv', 'data-bd-away': tab === 'adv' ? undefined : '' },
|
|
752
|
+
h('div', { 'data-bd-row': '' },
|
|
753
|
+
h('button', { 'data-bd-btn': '', disabled: busy === 'diag', onClick: onDiagnose }, '复制诊断信息'),
|
|
754
|
+
h('button', { 'data-bd-btn': '', disabled: busy === 'selfcheck', onClick: onSelfCheck }, '环境自检'),
|
|
755
|
+
h('div', { 'data-bd-note': '' }, '出问题时先点这两个:第一个把环境+日志拼成一段(已自动复制),第二个逐项告诉你哪一步不通。')),
|
|
756
|
+
checks && checks.length ? h('div', { 'data-bd-list': '', 'data-bd-checks': '' },
|
|
757
|
+
checks.map((x) => h('div', { key: x.name, 'data-bd-item': '', 'data-bd-item-t': '' },
|
|
758
|
+
h('div', { 'data-bd-item-t': '' }, (x.ok ? '正常 · ' : '有问题 · ') + x.name),
|
|
759
|
+
h('div', { 'data-bd-item-n': '' }, x.detail || ''),
|
|
760
|
+
x.ok ? null : h('div', { 'data-bd-item-why': '' }, '怎么办:' + (x.fix || '把诊断信息发给作者'))))) : null,
|
|
761
|
+
diag ? h('pre', { 'data-bd-pre': '' }, diag) : null,
|
|
517
762
|
h('div', { 'data-bd-h': '' }, '环境与账号'),
|
|
518
763
|
h('div', { 'data-bd-row': '' },
|
|
519
764
|
h('button', { 'data-bd-btn': '', disabled: busy === 'install', onClick: onInstall },
|
|
@@ -535,9 +780,12 @@ window.__ModuleLoader__.load({
|
|
|
535
780
|
'手机 B站 → 我的 → 扫一扫。',
|
|
536
781
|
h('br'),
|
|
537
782
|
'确认时选「记住我 / 长期」那张 —— 选 24 小时的话,明天就得重扫。')) : null),
|
|
783
|
+
(Array.isArray(st.missingDeps) && st.missingDeps.length)
|
|
784
|
+
? h('div', { 'data-bd-note': '' }, '还缺这几项:' + st.missingDeps.join(' / ') + '(qrcode = 画登录二维码用的)—— 点一次「重装主依赖」就会补上')
|
|
785
|
+
: null,
|
|
538
786
|
|
|
539
787
|
/* ── 它是怎么干活的(给他看的) ── */
|
|
540
|
-
h('div', { 'data-bd-sec': '' },
|
|
788
|
+
h('div', { 'data-bd-sec': '', 'data-bd-group': 'adv', 'data-bd-away': tab === 'adv' ? undefined : '' },
|
|
541
789
|
h('div', { 'data-bd-h': '' }, '它的地盘'),
|
|
542
790
|
h('div', { 'data-bd-kv': '' },
|
|
543
791
|
`家目录:${st.home}`,
|
|
@@ -571,6 +819,17 @@ window.__ModuleLoader__.load({
|
|
|
571
819
|
{ name: 'settings.section', id: 'bili-scroll', order: 30, label: '刷B站' },
|
|
572
820
|
() => h(Panel),
|
|
573
821
|
))
|
|
822
|
+
|
|
823
|
+
// ⭐ 2026-10-20 实验:会话头第三个 tab(0.2.1 里这个槽位真实存在;老版本没有就静默跳过)
|
|
824
|
+
try {
|
|
825
|
+
slots.inject('conversation.view', () => slots.register({
|
|
826
|
+
name: 'conversation.view',
|
|
827
|
+
id: 'bili-watched',
|
|
828
|
+
order: 20,
|
|
829
|
+
label: '刷过的视频',
|
|
830
|
+
scope: 'session',
|
|
831
|
+
}, () => h(WatchTab)))
|
|
832
|
+
} catch (e) { /* 没有这个槽位就算了 */ }
|
|
574
833
|
},
|
|
575
834
|
}
|
|
576
835
|
},
|