dsh-foxy-weibo 0.2.2 → 0.2.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/lib/client.js CHANGED
@@ -213,6 +213,10 @@ window.__ModuleLoader__.load({
213
213
  const data = await request('/run', { method: 'POST', body: '{}' })
214
214
  setStatus(data.status)
215
215
  const result = data.result || {}
216
+ if (result.error) {
217
+ setError(`检查失败:${result.error}`)
218
+ return
219
+ }
216
220
  setNotice(
217
221
  result.skipped === 'disabled'
218
222
  ? '未开始:自动浏览已关闭'
@@ -220,7 +224,8 @@ window.__ModuleLoader__.load({
220
224
  ? '正在运行,请稍候'
221
225
  : result.skipped === 'no-target'
222
226
  ? '未开始:请先填写监控账号 UID'
223
- : `运行完成:检查 ${result.checked || 0} 条,回复 ${result.replied || 0} 条` +
227
+ : `运行完成:博文 ${result.posts || 0} 篇、评论 ${result.comments || 0} 条,` +
228
+ `处理 ${result.checked || 0} 条,回复 ${result.replied || 0} 条` +
224
229
  (result.previewed ? `,预览 ${result.previewed} 条` : '') +
225
230
  `,跳过 ${result.skipped || 0} 条`,
226
231
  )
@@ -233,12 +238,17 @@ window.__ModuleLoader__.load({
233
238
  const data = await request('/check-now', { method: 'POST', body: '{}' })
234
239
  setStatus(data.status)
235
240
  const result = data.result || {}
241
+ if (result.error) {
242
+ setError(`检查失败:${result.error}`)
243
+ return
244
+ }
236
245
  setNotice(
237
246
  result.skipped === 'no-target'
238
247
  ? '未开始:请先填写监控账号 UID'
239
248
  : result.skipped === 'busy'
240
249
  ? '正在运行,请稍候'
241
- : `检查完成:检查 ${result.checked || 0} 条评论,预览 ${result.previewed || 0} 条,跳过 ${result.skipped || 0} 条` +
250
+ : `检查完成:博文 ${result.posts || 0} 篇、评论 ${result.comments || 0} 条,` +
251
+ `待处理 ${result.checked || 0} 条,预览 ${result.previewed || 0} 条,跳过 ${result.skipped || 0} 条` +
242
252
  (result.errors ? `,失败 ${result.errors} 条` : ''),
243
253
  )
244
254
  } catch (e) { setError(e.message) } finally { setBusy('') }
@@ -251,12 +261,16 @@ window.__ModuleLoader__.load({
251
261
  const data = await request('/reply-now', { method: 'POST', body: '{}' })
252
262
  setStatus(data.status)
253
263
  const result = data.result || {}
264
+ if (result.error) {
265
+ setError(`回复失败:${result.error}`)
266
+ return
267
+ }
254
268
  setNotice(
255
269
  result.replied > 0
256
270
  ? `已立即回复 ${result.replied} 条,可在下方日志查看`
257
271
  : result.skipped === 'no-target'
258
272
  ? '未开始:请先填写监控账号 UID'
259
- : '没有可立即回复的评论(可能已处理或模型选择跳过)',
273
+ : `已浏览 ${result.posts || 0} 篇博文、${result.comments || 0} 条评论,但没有可立即回复的新评论(可能已处理或模型选择跳过)`,
260
274
  )
261
275
  } catch (e) { setError(e.message) } finally { setBusy('') }
262
276
  }
@@ -325,8 +339,8 @@ window.__ModuleLoader__.load({
325
339
  h('option', { value: 'off' }, '仅检查,不回复'),
326
340
  h('option', { value: 'auto' }, '自动回复'),
327
341
  )),
328
- h(Field, { label: '每轮检查博文数', hint: '范围 1–20' },
329
- h('input', { type: 'number', min: 1, max: 20, value: settings.maxPostsPerRun, onChange: (e) => setField('maxPostsPerRun', e.target.value) })),
342
+ h(Field, { label: '浏览范围(博文条数)', hint: '按时间倒序检查;1–100,填 100 即尽量浏览全部' },
343
+ h('input', { type: 'number', min: 1, max: 100, value: settings.maxPostsPerRun, onChange: (e) => setField('maxPostsPerRun', e.target.value) })),
330
344
  h(Field, { label: '每日回复上限', hint: '0–10,同时限制评论' },
331
345
  h('input', { type: 'number', min: 0, max: 10, value: settings.maxCommentsPerDay, onChange: (e) => setField('maxCommentsPerDay', e.target.value) })),
332
346
  ),
package/lib/index.js CHANGED
@@ -164,12 +164,6 @@ async function scanOwnComments(ctx, config, state, log, force = false, options =
164
164
  const forceReply = options.forceReply === true
165
165
  const canReply = !previewOnly && (forceReply || config.replyMode === 'auto')
166
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()
170
- const maxPosts = Math.max(1, Math.min(20, Number(config.maxPostsPerRun) || 5))
171
- const maxReplies = Math.max(1, Math.min(8, Number(options.maxReplies ?? config.maxCommentsPerDay) || 1))
172
- const ignoreQuota = forceReply
173
167
  const result = {
174
168
  mode: options.mode || 'own-comments',
175
169
  checked: 0,
@@ -177,11 +171,36 @@ async function scanOwnComments(ctx, config, state, log, force = false, options =
177
171
  previewed: 0,
178
172
  skipped: 0,
179
173
  errors: 0,
174
+ posts: 0,
175
+ comments: 0,
180
176
  items: [],
181
177
  }
178
+ const targetUid = String(config.targetUid || '').trim()
179
+ if (!targetUid) return { skipped: 'no-target' }
180
+ let loginUid = String(config.accountUid || '').trim()
181
+ if (config.cookie) {
182
+ const account = await fetchAccount(config.cookie)
183
+ if (!account.uid) {
184
+ return {
185
+ ...result,
186
+ errors: 1,
187
+ error: '微博登录态无效,请重新扫码登录后再检查',
188
+ }
189
+ }
190
+ if (!loginUid) loginUid = account.uid
191
+ } else {
192
+ return { ...result, errors: 1, error: '请先扫码登录微博账号' }
193
+ }
194
+ const maxPosts = Math.max(1, Math.min(100, Number(config.maxPostsPerRun) || 5))
195
+ const maxReplies = Math.max(1, Math.min(8, Number(options.maxReplies ?? config.maxCommentsPerDay) || 1))
196
+ const ignoreQuota = forceReply
182
197
  let posts = []
183
198
  try {
184
- posts = await fetchTimeline(targetUid, config)
199
+ posts = await fetchTimeline(targetUid, config, {
200
+ maxPosts,
201
+ maxPages: Math.ceil(maxPosts / 10) + 1,
202
+ })
203
+ result.posts = posts.length
185
204
  } catch (error) {
186
205
  return { ...result, errors: 1, error: error?.message || String(error) }
187
206
  }
@@ -196,6 +215,7 @@ async function scanOwnComments(ctx, config, state, log, force = false, options =
196
215
  log(`[foxy-weibo] 读取评论失败 ${post.id}:${error?.message || error}`)
197
216
  continue
198
217
  }
218
+ result.comments += comments.length
199
219
  for (const comment of comments) {
200
220
  if (targetCid && String(comment.cid) !== targetCid) continue
201
221
  if (canReply && result.replied >= maxReplies) break
package/lib/settings.js CHANGED
@@ -97,7 +97,7 @@ function clampNumber(key, value, fallback) {
97
97
  const n = Number(value)
98
98
  if (!Number.isFinite(n)) return fallback
99
99
  if (key === 'intervalMinutes') return Math.max(5, Math.min(24 * 60, Math.round(n)))
100
- if (key === 'maxPostsPerRun') return Math.max(1, Math.min(20, Math.round(n)))
100
+ if (key === 'maxPostsPerRun') return Math.max(1, Math.min(100, Math.round(n)))
101
101
  if (key === 'maxCommentsPerDay') return Math.max(0, Math.min(10, Math.round(n)))
102
102
  return n
103
103
  }
package/lib/weibo.js CHANGED
@@ -100,11 +100,11 @@ export function normalizePost(raw) {
100
100
  }
101
101
  }
102
102
 
103
- async function fetchWebTimeline(uid, config = {}) {
103
+ async function fetchWebTimeline(uid, config = {}, page = 1) {
104
104
  const target = String(uid || '').trim()
105
105
  const url =
106
106
  `https://weibo.com/ajax/statuses/mymblog?uid=${encodeURIComponent(target)}` +
107
- `&page=1&feature=0`
107
+ `&page=${Math.max(1, page)}&feature=0`
108
108
  const response = await fetch(url, { headers: headersFor(config, false) })
109
109
  if (!response.ok) throw new WeiboError(`读取微博失败:HTTP ${response.status}`, 'HTTP')
110
110
  const text = await response.text()
@@ -122,39 +122,70 @@ async function fetchWebTimeline(uid, config = {}) {
122
122
  return list.map(normalizePost).filter((post) => post.id && post.text)
123
123
  }
124
124
 
125
- export async function fetchTimeline(uid, config = {}) {
125
+ function uniquePosts(posts) {
126
+ const seen = new Set()
127
+ const out = []
128
+ for (const post of posts) {
129
+ const key = post.id || post.mid
130
+ if (!key || seen.has(key)) continue
131
+ seen.add(key)
132
+ out.push(post)
133
+ }
134
+ return out
135
+ }
136
+
137
+ export async function fetchTimeline(uid, config = {}, options = {}) {
126
138
  const target = String(uid || '').trim()
127
139
  if (!/^\d{5,20}$/.test(target)) throw new WeiboError('微博 UID 格式不正确', 'BAD_UID')
128
- const url =
129
- `https://m.weibo.cn/api/container/getIndex?type=uid&value=${encodeURIComponent(target)}` +
130
- `&containerid=107603${encodeURIComponent(target)}&page=1`
131
- try {
132
- const response = await fetch(url, { headers: headersFor(config, true) })
133
- if (!response.ok) throw new WeiboError(`读取微博失败:HTTP ${response.status}`, 'HTTP')
134
- const json = await readJson(response)
135
- const cards = Array.isArray(json?.data?.cards) ? json.data.cards : []
136
- return cards
137
- .filter((card) => card?.mblog)
138
- .map((card) => normalizePost(card.mblog))
139
- .filter((post) => post.id && post.text)
140
- } catch (error) {
141
- if (config.cookie) {
142
- try {
143
- return await fetchWebTimeline(target, config)
144
- } catch (webError) {
145
- throw new WeiboError(
146
- `${error.message};备用接口也失败:${webError.message}`,
147
- 'FETCH_FAILED',
148
- webError.detail,
149
- )
140
+ const maxPosts = Math.max(1, Math.min(100, Number(options.maxPosts) || 20))
141
+ const maxPages = Math.max(1, Math.min(20, Number(options.maxPages) || Math.ceil(maxPosts / 10) + 1))
142
+ const collected = []
143
+
144
+ for (let page = 1; page <= maxPages && collected.length < maxPosts; page += 1) {
145
+ const url =
146
+ `https://m.weibo.cn/api/container/getIndex?type=uid&value=${encodeURIComponent(target)}` +
147
+ `&containerid=107603${encodeURIComponent(target)}&page=${page}`
148
+ try {
149
+ const response = await fetch(url, { headers: headersFor(config, true) })
150
+ if (!response.ok) throw new WeiboError(`读取微博失败:HTTP ${response.status}`, 'HTTP')
151
+ const json = await readJson(response)
152
+ const cards = Array.isArray(json?.data?.cards) ? json.data.cards : []
153
+ const posts = uniquePosts(
154
+ cards
155
+ .filter((card) => card?.mblog)
156
+ .map((card) => normalizePost(card.mblog))
157
+ .filter((post) => post.id && post.text),
158
+ )
159
+ if (!posts.length) break
160
+ collected.push(...posts)
161
+ if (posts.length < 10) break
162
+ } catch (error) {
163
+ if (config.cookie) {
164
+ try {
165
+ const posts = uniquePosts(await fetchWebTimeline(target, config, page))
166
+ if (!posts.length) break
167
+ collected.push(...posts)
168
+ if (posts.length < 10) break
169
+ continue
170
+ } catch (webError) {
171
+ if (collected.length) break
172
+ throw new WeiboError(
173
+ `${error.message};备用接口也失败:${webError.message}`,
174
+ 'FETCH_FAILED',
175
+ webError.detail,
176
+ )
177
+ }
150
178
  }
179
+ if (collected.length) break
180
+ throw new WeiboError(
181
+ `${error.message};微博接口可能要求登录 Cookie(设置 → Foxy-weibo → 微博 Cookie)`,
182
+ 'LOGIN_REQUIRED',
183
+ error.detail,
184
+ )
151
185
  }
152
- throw new WeiboError(
153
- `${error.message};微博接口可能要求登录 Cookie(设置 → Foxy-weibo → 微博 Cookie)`,
154
- 'LOGIN_REQUIRED',
155
- error.detail,
156
- )
157
186
  }
187
+
188
+ return uniquePosts(collected).slice(0, maxPosts)
158
189
  }
159
190
 
160
191
  function normalizeComment(raw) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-foxy-weibo",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",
@@ -48,3 +48,5 @@
48
48
  "test": "node test/test-weibo.cjs && node test/test-client.cjs"
49
49
  }
50
50
  }
51
+
52
+