koishi-plugin-foxy-daily 0.2.3 → 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/comments.js +43 -14
- package/package.json +1 -1
package/lib/comments.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { createHash } = require('node:crypto')
|
|
3
4
|
const { generateReply } = require('./digest')
|
|
4
5
|
|
|
5
6
|
function formatAuthor(comment) {
|
|
@@ -10,8 +11,21 @@ function flattenComments(post) {
|
|
|
10
11
|
return Array.isArray(post.comments) ? post.comments : []
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function commentKey(comment) {
|
|
15
|
+
const id = String(comment?.id || '')
|
|
16
|
+
const kind = String(comment?.kind || 'comment')
|
|
17
|
+
const target = String(comment?.replyTo?.id ?? comment?.replyToUser?.id ?? comment?.parentId ?? '')
|
|
18
|
+
const author = String(comment?.author?.id || '')
|
|
19
|
+
const content = String(comment?.content || '')
|
|
20
|
+
const hash = createHash('sha1').update(content).digest('hex').slice(0, 10)
|
|
21
|
+
return `${id}:${kind}:${target}:${author}:${hash}`
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
function findComment(post, commentId) {
|
|
14
|
-
|
|
25
|
+
const value = String(commentId)
|
|
26
|
+
return flattenComments(post).find(
|
|
27
|
+
(comment) => commentKey(comment) === value || String(comment.id) === value,
|
|
28
|
+
)
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
function normalizeIds(values) {
|
|
@@ -123,6 +137,17 @@ class CommentRunner {
|
|
|
123
137
|
return posts
|
|
124
138
|
}
|
|
125
139
|
|
|
140
|
+
_commentState(post, comment, comments = flattenComments(post)) {
|
|
141
|
+
const stableKey = commentKey(comment)
|
|
142
|
+
const direct = this.state.commentState(post.id, stableKey)
|
|
143
|
+
if (direct) return direct
|
|
144
|
+
const legacy = this.state.commentState(post.id, String(comment.id))
|
|
145
|
+
if (!legacy) return undefined
|
|
146
|
+
const siblings = comments.filter((item) => String(item.id) === String(comment.id))
|
|
147
|
+
if (siblings.length <= 1) return legacy
|
|
148
|
+
return siblings[0] === comment ? legacy : undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
126
151
|
async listComments(postId) {
|
|
127
152
|
const posts = await this._targets(postId)
|
|
128
153
|
const lines = []
|
|
@@ -133,7 +158,7 @@ class CommentRunner {
|
|
|
133
158
|
const comments = flattenComments(post)
|
|
134
159
|
if (comments.length === 0) lines.push(' (无评论)')
|
|
135
160
|
for (const comment of comments) {
|
|
136
|
-
const state = this.
|
|
161
|
+
const state = this._commentState(post, comment, comments)
|
|
137
162
|
const mark = state?.status === 'replied' ? '已回复' : state?.status === 'unknown' ? '待确认' : '未回复'
|
|
138
163
|
const nested = comment.kind === 'reply'
|
|
139
164
|
? `,回复 ${comment.replyToUser?.nickname || comment.replyTo?.id || '?'}`
|
|
@@ -151,10 +176,11 @@ class CommentRunner {
|
|
|
151
176
|
const post = await this.qzone.getPost(summary)
|
|
152
177
|
if (!this._isOwnPost(post) && !this._otherPostAllowed(post)) continue
|
|
153
178
|
const pending = []
|
|
154
|
-
|
|
179
|
+
const comments = flattenComments(post)
|
|
180
|
+
for (const comment of comments) {
|
|
155
181
|
if (!this._commentAllowed(post, comment)) continue
|
|
156
182
|
if (!this._commenterAllowed(post, comment)) continue
|
|
157
|
-
const existing = this.
|
|
183
|
+
const existing = this._commentState(post, comment, comments)
|
|
158
184
|
if (existing && ['replied', 'processing', 'unknown', 'skipped'].includes(existing.status)) continue
|
|
159
185
|
if ((existing?.attempts || 0) >= (this.config.commentMaxAttempts || 3)) continue
|
|
160
186
|
const ageMs = comment.createdAt ? Date.now() - new Date(comment.createdAt).getTime() : 0
|
|
@@ -179,8 +205,9 @@ class CommentRunner {
|
|
|
179
205
|
const comment = findComment(post, commentId)
|
|
180
206
|
if (!comment) throw new Error(`动态 ${postId} 下找不到评论 ${commentId}`)
|
|
181
207
|
if (!this._commentAllowed(post, comment)) throw new Error('这条评论不在允许回复的范围内')
|
|
208
|
+
const key = commentKey(comment)
|
|
182
209
|
const result = await this.qzone.reply(post, comment, content)
|
|
183
|
-
this.state.setCommentState(post.id,
|
|
210
|
+
this.state.setCommentState(post.id, key, {
|
|
184
211
|
status: result.outcome === 'unknown' ? 'unknown' : 'replied',
|
|
185
212
|
outcome: result.outcome,
|
|
186
213
|
reply: content,
|
|
@@ -191,8 +218,9 @@ class CommentRunner {
|
|
|
191
218
|
}
|
|
192
219
|
|
|
193
220
|
async _reply(post, comment) {
|
|
194
|
-
const
|
|
195
|
-
this.
|
|
221
|
+
const key = commentKey(comment)
|
|
222
|
+
const existing = this._commentState(post, comment)
|
|
223
|
+
this.state.setCommentState(post.id, key, {
|
|
196
224
|
status: 'processing',
|
|
197
225
|
attempts: (existing?.attempts || 0) + 1,
|
|
198
226
|
})
|
|
@@ -201,7 +229,7 @@ class CommentRunner {
|
|
|
201
229
|
const persona = await this.getPersona()
|
|
202
230
|
const content = await generateReply({ model, post, comment, persona })
|
|
203
231
|
if (!content) {
|
|
204
|
-
this.state.setCommentState(post.id,
|
|
232
|
+
this.state.setCommentState(post.id, key, {
|
|
205
233
|
status: 'failed',
|
|
206
234
|
attempts: (existing?.attempts || 0) + 1,
|
|
207
235
|
error: 'empty reply',
|
|
@@ -210,7 +238,7 @@ class CommentRunner {
|
|
|
210
238
|
return { replied: false, reason: 'empty' }
|
|
211
239
|
}
|
|
212
240
|
const result = await this.qzone.reply(post, comment, content)
|
|
213
|
-
this.state.setCommentState(post.id,
|
|
241
|
+
this.state.setCommentState(post.id, key, {
|
|
214
242
|
status: result.outcome === 'unknown' ? 'unknown' : 'replied',
|
|
215
243
|
outcome: result.outcome,
|
|
216
244
|
reply: content,
|
|
@@ -232,18 +260,19 @@ class CommentRunner {
|
|
|
232
260
|
for (const summary of posts) {
|
|
233
261
|
const post = await this.qzone.getPost(summary)
|
|
234
262
|
if (!this._isOwnPost(post) && !this._otherPostAllowed(post)) continue
|
|
235
|
-
|
|
263
|
+
const comments = flattenComments(post)
|
|
264
|
+
for (const comment of comments) {
|
|
236
265
|
if (!this._commentAllowed(post, comment)) continue
|
|
237
266
|
if (!this._commenterAllowed(post, comment)) {
|
|
238
267
|
report.skipped++
|
|
239
268
|
continue
|
|
240
269
|
}
|
|
241
|
-
const existing = this.
|
|
270
|
+
const existing = this._commentState(post, comment, comments)
|
|
242
271
|
if (existing && ['replied', 'processing', 'unknown', 'skipped'].includes(existing.status)) continue
|
|
243
272
|
if ((existing?.attempts || 0) >= (this.config.commentMaxAttempts || 3)) continue
|
|
244
273
|
const ageMs = comment.createdAt ? Date.now() - new Date(comment.createdAt).getTime() : 0
|
|
245
274
|
if (ageMs > (this.config.commentMaxAgeHours || 72) * 3600_000) {
|
|
246
|
-
this.state.setCommentState(post.id, comment
|
|
275
|
+
this.state.setCommentState(post.id, commentKey(comment), { status: 'skipped', reason: 'too-old' })
|
|
247
276
|
await this.state.save()
|
|
248
277
|
report.skipped++
|
|
249
278
|
continue
|
|
@@ -259,7 +288,7 @@ class CommentRunner {
|
|
|
259
288
|
report.skipped++
|
|
260
289
|
}
|
|
261
290
|
} catch (error) {
|
|
262
|
-
this.state.setCommentState(post.id, comment
|
|
291
|
+
this.state.setCommentState(post.id, commentKey(comment), {
|
|
263
292
|
status: 'failed',
|
|
264
293
|
attempts: (existing?.attempts || 0) + 1,
|
|
265
294
|
error: error.message,
|
|
@@ -276,4 +305,4 @@ class CommentRunner {
|
|
|
276
305
|
}
|
|
277
306
|
}
|
|
278
307
|
|
|
279
|
-
module.exports = { CommentRunner, flattenComments, findComment }
|
|
308
|
+
module.exports = { CommentRunner, commentKey, flattenComments, findComment }
|
package/package.json
CHANGED