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 +19 -5
- package/lib/index.js +27 -7
- package/lib/settings.js +1 -1
- package/lib/weibo.js +61 -30
- package/package.json +3 -1
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
|
-
:
|
|
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
|
-
:
|
|
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: '
|
|
329
|
-
h('input', { type: 'number', min: 1, max:
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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.
|
|
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
|
+
|