dsh-foxy-weibo 0.2.0 → 0.2.2

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/README.md CHANGED
@@ -6,7 +6,7 @@ Foxy 的微博子插件:按 foxy3 预设关注微博账号、阅读博文、
6
6
 
7
7
  ## 功能
8
8
 
9
- - **主模式**:检查自己的微博 UID,读取自己新博文下的评论,只回复未回复的他人评论;
9
+ - **博文评论回复**:检查指定的监控账号 UID,读取其新博文下的评论,只回复未处理的他人评论;登录账号是机器人自己,监控账号通常是主人账号;
10
10
  - 可设置每日检查时间(`HH:MM`,最多 6 个),例如 `08:30, 20:00`;
11
11
  - 回复前按 foxy3 预设判断 `skip / comment`,不设最少字数,最多 80 字;
12
12
  - 旧模式保留:关注一个或多个微博 UID,定时读取时间线并评论;
@@ -47,7 +47,7 @@ Cookie 只保存在本机 `~/.dsh/foxy-weibo/settings.json`,不会回传给前
47
47
 
48
48
  ### 自己的 UID / 关注 UID
49
49
 
50
- `自己的微博 UID` 是主模式使用的账号,插件只检查它的新博文和评论。
50
+ `监控账号 UID` 是要检查博文评论的目标账号(通常是主人账号);`当前账号` 是扫码登录的机器人账号,两者可以不同。
51
51
 
52
52
  `关注 UID` 是旧模式使用,多个 UID 用逗号分隔,最多 20 个。UID 是微博用户主页 URL 里的数字 ID,例如 `https://weibo.com/u/1234567890`。
53
53
 
@@ -63,10 +63,10 @@ Cookie 只保存在本机 `~/.dsh/foxy-weibo/settings.json`,不会回传给前
63
63
 
64
64
  ## 建议顺序
65
65
 
66
- 1. 扫码登录,填自己的微博 UID,`enabled: true`、`replyMode: off`;
66
+ 1. 扫码登录,填监控账号 UID,`enabled: true`、`replyMode: off`;
67
67
  2. 设置每日检查时间,或先点「立即运行」,看日志和「调试」里的模型决策;
68
68
  3. 确认判断正常后,再开启 `replyMode: auto`,每日上限先设为 1;
69
- 4. 旧模式需要浏览他人博文时,再关闭「主模式」并配置关注 UID;
69
+ 4. 「立即检查」只判断不发布,用来预览回复;「立即回复」会真实发布一条,适合测试;
70
70
  5. 微博评论有账号风控,不要高频、不要对陌生人全自动回复。
71
71
 
72
72
  ## 记忆事件
package/cordis.patch.yml CHANGED
@@ -10,7 +10,7 @@
10
10
  config:
11
11
  enabled: false
12
12
  cookie: ''
13
- selfUid: ''
13
+ targetUid: ''
14
14
  targetUids: []
15
15
  replyOwnPosts: true
16
16
  replyMode: 'off'
package/lib/client.js CHANGED
@@ -218,8 +218,8 @@ window.__ModuleLoader__.load({
218
218
  ? '未开始:自动浏览已关闭'
219
219
  : result.skipped === 'busy'
220
220
  ? '正在运行,请稍候'
221
- : result.skipped === 'no-self'
222
- ? '未开始:请先填写自己的微博 UID'
221
+ : result.skipped === 'no-target'
222
+ ? '未开始:请先填写监控账号 UID'
223
223
  : `运行完成:检查 ${result.checked || 0} 条,回复 ${result.replied || 0} 条` +
224
224
  (result.previewed ? `,预览 ${result.previewed} 条` : '') +
225
225
  `,跳过 ${result.skipped || 0} 条`,
@@ -227,6 +227,40 @@ window.__ModuleLoader__.load({
227
227
  } catch (e) { setError(e.message) } finally { setBusy('') }
228
228
  }
229
229
 
230
+ const checkNow = async () => {
231
+ setBusy('check'); setError(''); setNotice('')
232
+ try {
233
+ const data = await request('/check-now', { method: 'POST', body: '{}' })
234
+ setStatus(data.status)
235
+ const result = data.result || {}
236
+ setNotice(
237
+ result.skipped === 'no-target'
238
+ ? '未开始:请先填写监控账号 UID'
239
+ : result.skipped === 'busy'
240
+ ? '正在运行,请稍候'
241
+ : `检查完成:检查 ${result.checked || 0} 条评论,预览 ${result.previewed || 0} 条,跳过 ${result.skipped || 0} 条` +
242
+ (result.errors ? `,失败 ${result.errors} 条` : ''),
243
+ )
244
+ } catch (e) { setError(e.message) } finally { setBusy('') }
245
+ }
246
+
247
+ const replyNow = async () => {
248
+ if (!window.confirm('立即回复会真实发布到微博,确定继续?')) return
249
+ setBusy('reply'); setError(''); setNotice('')
250
+ try {
251
+ const data = await request('/reply-now', { method: 'POST', body: '{}' })
252
+ setStatus(data.status)
253
+ const result = data.result || {}
254
+ setNotice(
255
+ result.replied > 0
256
+ ? `已立即回复 ${result.replied} 条,可在下方日志查看`
257
+ : result.skipped === 'no-target'
258
+ ? '未开始:请先填写监控账号 UID'
259
+ : '没有可立即回复的评论(可能已处理或模型选择跳过)',
260
+ )
261
+ } catch (e) { setError(e.message) } finally { setBusy('') }
262
+ }
263
+
230
264
  if (!settings) return h('div', { className: 'fw-page' }, h('p', { className: 'fw-muted' }, error || '正在读取微博设置…'))
231
265
 
232
266
  const recent = Array.isArray(status?.recent) ? status.recent : []
@@ -238,7 +272,7 @@ window.__ModuleLoader__.load({
238
272
  h('div', { className: 'fw-head' },
239
273
  h('div', null,
240
274
  h('p', { className: 'fw-eyebrow' }, 'FOXY EXTENSION'),
241
- h('h1', null, '微博浏览'),
275
+ h('h1', null, '微博评论'),
242
276
  h('p', null, 'Foxy 按 foxy3 预设关注微博账号,阅读、判断、评论并写入记忆。'),
243
277
  ),
244
278
  h('span', { className: 'fw-badge' }, settings.enabled ? '自动浏览已开启' : '自动浏览已关闭'),
@@ -250,8 +284,8 @@ window.__ModuleLoader__.load({
250
284
  h(Field, { label: '当前账号', hint: '扫码成功后自动显示' },
251
285
  h('input', { value: settings.accountName ? `${settings.accountName}(${settings.accountUid || '?'})` : settings.cookieSet ? '已登录' : '未登录', readOnly: true }),
252
286
  ),
253
- h(Field, { label: '自己的微博 UID', hint: '主功能会检查这个账号的新博文评论' },
254
- h('input', { value: settings.selfUid || '', placeholder: '例如 1234567890', onChange: (e) => setField('selfUid', e.target.value) })),
287
+ h(Field, { label: '监控账号 UID', hint: '要检查博文评论的目标账号;不是登录的机器人账号' },
288
+ h('input', { value: settings.targetUid || '', placeholder: '例如 1234567890', onChange: (e) => setField('targetUid', e.target.value) })),
255
289
  ),
256
290
  h('div', { className: 'fw-actions' },
257
291
  h('button', { className: 'fw-btn primary', disabled: !!busy, onClick: startLogin }, busy === 'login' ? '获取二维码…' : qr ? '刷新二维码' : '扫码登录'),
@@ -280,9 +314,9 @@ window.__ModuleLoader__.load({
280
314
  ),
281
315
 
282
316
  h('section', { className: 'fw-card' },
283
- h(SectionHead, { title: '回复自己的博文', description: '主功能:每天按设定时间检查自己的新博文,只回复未回复的他人评论。' }),
317
+ h(SectionHead, { title: '博文评论回复', description: '每日按设定时间检查目标账号的新博文,并回复其评论区中尚未处理的他人评论。' }),
284
318
  h(Toggle, { checked: settings.enabled, onChange: (v) => setField('enabled', v), label: '启用自动检查', hint: '关闭后定时任务不运行,仍可使用「立即运行」。' }),
285
- h(Toggle, { checked: settings.replyOwnPosts, onChange: (v) => setField('replyOwnPosts', v), label: '主模式:只看自己的博文评论', hint: '关闭后回到旧模式,浏览关注账号的时间线并评论博文。' }),
319
+ h(Toggle, { checked: settings.replyOwnPosts, onChange: (v) => setField('replyOwnPosts', v), label: '仅监控目标账号的博文', hint: '关闭后回到旧模式,浏览关注账号的时间线并评论博文。' }),
286
320
  h('div', { className: 'fw-grid' },
287
321
  h(Field, { label: '每日检查时间', hint: 'HH:MM,多个用逗号分隔,最多 6 个,例如 08:30, 20:00' },
288
322
  h('input', { value: scheduleText, placeholder: '20:00', onChange: (e) => setScheduleText(e.target.value) })),
@@ -297,7 +331,7 @@ window.__ModuleLoader__.load({
297
331
  h('input', { type: 'number', min: 0, max: 10, value: settings.maxCommentsPerDay, onChange: (e) => setField('maxCommentsPerDay', e.target.value) })),
298
332
  ),
299
333
  h('div', { className: 'fw-grid' },
300
- h(Field, { label: '时间线轮询(旧模式备用)', hint: '关闭主模式时使用,分钟' },
334
+ h(Field, { label: '时间线轮询(旧模式备用)', hint: '关闭“仅监控目标账号的博文”时使用,分钟' },
301
335
  h('input', { type: 'number', min: 5, max: 1440, value: settings.intervalMinutes, onChange: (e) => setField('intervalMinutes', e.target.value) })),
302
336
  h(Field, { label: '旧模式评论模式', hint: '浏览他人博文时是否发一级评论' },
303
337
  h('select', { value: settings.commentMode, onChange: (e) => setField('commentMode', e.target.value) },
@@ -311,6 +345,8 @@ window.__ModuleLoader__.load({
311
345
  h(Toggle, { checked: settings.debug, onChange: (v) => setField('debug', v), label: '记录调试日志', hint: '保存模型输出与记忆写入结果,便于排查。' }),
312
346
  h('div', { className: 'fw-actions' },
313
347
  h('button', { className: 'fw-btn primary', disabled: !!busy, onClick: runNow }, busy === 'run' ? '正在运行…' : '立即运行'),
348
+ h('button', { className: 'fw-btn', disabled: !!busy, onClick: checkNow }, busy === 'check' ? '检查中…' : '立即检查'),
349
+ h('button', { className: 'fw-btn', disabled: !!busy, onClick: replyNow }, busy === 'reply' ? '回复中…' : '立即回复'),
314
350
  h('button', { className: 'fw-btn', disabled: !!busy, onClick: save }, busy === 'save' ? '保存中…' : '保存设置'),
315
351
  ),
316
352
  h('div', { className: 'fw-stats' },
package/lib/index.js CHANGED
@@ -11,6 +11,8 @@ export const inject = ['connection']
11
11
  export const STATUS_ROUTE = '/api/dsh-foxy-weibo/status'
12
12
  export const SETTINGS_ROUTE = '/api/dsh-foxy-weibo/settings'
13
13
  export const RUN_ROUTE = '/api/dsh-foxy-weibo/run'
14
+ export const CHECK_NOW_ROUTE = '/api/dsh-foxy-weibo/check-now'
15
+ export const REPLY_NOW_ROUTE = '/api/dsh-foxy-weibo/reply-now'
14
16
  export const LOGIN_START_ROUTE = '/api/dsh-foxy-weibo/login/start'
15
17
  export const LOGIN_POLL_ROUTE = '/api/dsh-foxy-weibo/login/poll'
16
18
  export const LOGOUT_ROUTE = '/api/dsh-foxy-weibo/logout'
@@ -156,15 +158,20 @@ async function rememberReply(ctx, config, state, post, comment, replyText) {
156
158
  }
157
159
  }
158
160
 
159
- async function scanOwnComments(ctx, config, state, log, force = false) {
161
+ async function scanOwnComments(ctx, config, state, log, force = false, options = {}) {
160
162
  if (!config.enabled && !force) return { skipped: 'disabled' }
161
- const canReply = config.replyMode === 'auto'
162
- const selfUid = String(config.selfUid || config.accountUid || '').trim()
163
- if (!selfUid) return { skipped: 'no-self' }
163
+ const previewOnly = options.previewOnly === true
164
+ const forceReply = options.forceReply === true
165
+ const canReply = !previewOnly && (forceReply || config.replyMode === 'auto')
166
+ const targetCid = String(options.cid || '').trim()
167
+ const targetUid = String(config.targetUid || '').trim()
168
+ if (!targetUid) return { skipped: 'no-target' }
169
+ const loginUid = String(config.accountUid || '').trim()
164
170
  const maxPosts = Math.max(1, Math.min(20, Number(config.maxPostsPerRun) || 5))
165
- const maxReplies = Math.max(1, Math.min(8, Number(config.maxCommentsPerDay) || 1))
171
+ const maxReplies = Math.max(1, Math.min(8, Number(options.maxReplies ?? config.maxCommentsPerDay) || 1))
172
+ const ignoreQuota = forceReply
166
173
  const result = {
167
- mode: 'own-comments',
174
+ mode: options.mode || 'own-comments',
168
175
  checked: 0,
169
176
  replied: 0,
170
177
  previewed: 0,
@@ -174,7 +181,7 @@ async function scanOwnComments(ctx, config, state, log, force = false) {
174
181
  }
175
182
  let posts = []
176
183
  try {
177
- posts = await fetchTimeline(selfUid, config)
184
+ posts = await fetchTimeline(targetUid, config)
178
185
  } catch (error) {
179
186
  return { ...result, errors: 1, error: error?.message || String(error) }
180
187
  }
@@ -190,14 +197,15 @@ async function scanOwnComments(ctx, config, state, log, force = false) {
190
197
  continue
191
198
  }
192
199
  for (const comment of comments) {
200
+ if (targetCid && String(comment.cid) !== targetCid) continue
193
201
  if (canReply && result.replied >= maxReplies) break
194
202
  if (!comment.cid || state.commentSeen[comment.cid]) continue
195
- if (String(comment.author?.uid || '') === selfUid) {
203
+ if (loginUid && String(comment.author?.uid || '') === loginUid) {
196
204
  state.commentSeen[comment.cid] = { at: Date.now(), status: 'self' }
197
205
  continue
198
206
  }
199
207
  const limit = Math.max(0, Number(config.maxCommentsPerDay) || 0)
200
- if (canReply && commentCount(state) >= limit) break
208
+ if (canReply && !ignoreQuota && commentCount(state) >= limit) break
201
209
  result.checked += 1
202
210
  let entry
203
211
  try {
@@ -474,11 +482,56 @@ export function apply(ctx, rawConfig = {}) {
474
482
  }
475
483
  }
476
484
 
485
+ const checkNow = async () => {
486
+ if (running) return { skipped: 'busy' }
487
+ running = true
488
+ try {
489
+ log('[foxy-weibo] 开始立即检查')
490
+ const result = await scanOwnComments(ctx, config, state, log, true, {
491
+ previewOnly: true,
492
+ mode: 'own-comments-check',
493
+ })
494
+ saveState(state)
495
+ log(`[foxy-weibo] 立即检查结束 ${JSON.stringify(result)}`)
496
+ return result
497
+ } catch (error) {
498
+ saveState(state)
499
+ ctx.logger?.warn?.(`[foxy-weibo] check failed: ${error?.message || error}`)
500
+ return { error: String(error?.message || error) }
501
+ } finally {
502
+ running = false
503
+ }
504
+ }
505
+
506
+ const replyNow = async (cid = '') => {
507
+ if (running) return { skipped: 'busy' }
508
+ running = true
509
+ try {
510
+ log(`[foxy-weibo] 开始立即回复${cid ? ` cid=${cid}` : ''}`)
511
+ const result = await scanOwnComments(ctx, config, state, log, true, {
512
+ forceReply: true,
513
+ maxReplies: 1,
514
+ cid,
515
+ mode: 'own-comments-reply-now',
516
+ })
517
+ saveState(state)
518
+ log(`[foxy-weibo] 立即回复结束 ${JSON.stringify(result)}`)
519
+ return result
520
+ } catch (error) {
521
+ saveState(state)
522
+ ctx.logger?.warn?.(`[foxy-weibo] reply-now failed: ${error?.message || error}`)
523
+ return { error: String(error?.message || error) }
524
+ } finally {
525
+ running = false
526
+ }
527
+ }
528
+
477
529
  const status = () => ({
478
530
  enabled: config.enabled === true,
479
531
  running,
480
532
  mode: config.replyOwnPosts === false ? 'timeline' : 'own-comments',
481
- selfUid: config.selfUid || config.accountUid || '',
533
+ targetUid: config.targetUid || '',
534
+ loginUid: config.accountUid || '',
482
535
  scheduleTimes: config.scheduleTimes,
483
536
  nextRunAt,
484
537
  replyMode: config.replyMode,
@@ -580,7 +633,6 @@ export function apply(ctx, rawConfig = {}) {
580
633
  config.cookie = result.cookie
581
634
  config.accountName = result.account?.screenName || ''
582
635
  config.accountUid = result.account?.uid || ''
583
- if (!config.selfUid && result.account?.uid) config.selfUid = result.account.uid
584
636
  saveSettings(config)
585
637
  }
586
638
  return json({ ok: true, ...result, settings: publicSettings(config), status: status() })
@@ -598,6 +650,17 @@ export function apply(ctx, rawConfig = {}) {
598
650
  const result = await run('manual', true)
599
651
  return json({ ok: true, result, status: status() })
600
652
  })
653
+
654
+ route(CHECK_NOW_ROUTE, ['POST'], async () => {
655
+ const result = await checkNow()
656
+ return json({ ok: true, result, status: status() })
657
+ })
658
+
659
+ route(REPLY_NOW_ROUTE, ['POST'], async (request) => {
660
+ const body = await readJsonBody(request)
661
+ const result = await replyNow(body?.cid)
662
+ return json({ ok: true, result, status: status() })
663
+ })
601
664
  })
602
665
 
603
666
  log(
package/lib/settings.js CHANGED
@@ -5,7 +5,7 @@ import { dirname, join } from 'node:path'
5
5
  export const DEFAULTS = {
6
6
  enabled: false,
7
7
  cookie: '',
8
- selfUid: '',
8
+ targetUid: '',
9
9
  targetUids: [],
10
10
  replyOwnPosts: true,
11
11
  replyMode: 'off', // off | auto
@@ -26,7 +26,7 @@ export const DEFAULTS = {
26
26
 
27
27
  export const SETTING_KEYS = [
28
28
  'enabled',
29
- 'selfUid',
29
+ 'targetUid',
30
30
  'targetUids',
31
31
  'replyOwnPosts',
32
32
  'replyMode',
@@ -45,7 +45,7 @@ export const SETTING_KEYS = [
45
45
  ]
46
46
 
47
47
  const TEXT_KEYS = new Set([
48
- 'selfUid',
48
+ 'targetUid',
49
49
  'commentMode',
50
50
  'replyMode',
51
51
  'memoryPresetId',
@@ -137,6 +137,8 @@ export function loadSettings(base = {}) {
137
137
  if (existsSync(settingsPath())) {
138
138
  const parsed = JSON.parse(readFileSync(settingsPath(), 'utf8'))
139
139
  next = normalizePatch(next, parsed)
140
+ // 0.2.x used selfUid for the monitored account; migrate it once.
141
+ if (!next.targetUid && typeof parsed.selfUid === 'string') next.targetUid = parsed.selfUid.trim()
140
142
  if (typeof parsed.cookie === 'string') next.cookie = parsed.cookie
141
143
  if (typeof parsed.llmApiKey === 'string') next.llmApiKey = parsed.llmApiKey
142
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-foxy-weibo",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Foxy's Weibo transport for DeepSeek Harness: follow users, read posts, comment, reply, and remember events.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",