@bunizao/contracts 0.10.0 → 0.11.0
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/dist/admin.d.ts +401 -2
- package/dist/admin.js +17 -0
- package/dist/comments.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/messages.d.ts +4 -1
- package/package.json +1 -1
package/dist/admin.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ClientFingerprint, CommentAuthAtWrite, CommentClaimMethod, CommentStatus, Interaction } from './comments';
|
|
1
|
+
import type { ClientFingerprint, CommentAuthAtWrite, CommentClaimMethod, CommentsMode, CommentStatus, CommentSurface, Interaction } from './comments';
|
|
2
|
+
import type { MessageLocale, MessageState } from './messages';
|
|
2
3
|
import type { DeliveryMode, NotifyAuditEventType, NotifyChannel, SubscriberRecord, SubscriberStatus } from './notify';
|
|
3
4
|
export interface SubscriberChannelCount {
|
|
4
5
|
total: number;
|
|
@@ -108,10 +109,30 @@ export interface AdminCommentRecord {
|
|
|
108
109
|
country: string | null;
|
|
109
110
|
createdAt: string;
|
|
110
111
|
editedAt: string | null;
|
|
111
|
-
/** Filled from the post registry; null when it could not say.
|
|
112
|
+
/** Filled from the post registry; null when it could not say. A mood
|
|
113
|
+
post has no slug: `postSlug` is always null for one, and `postPath`
|
|
114
|
+
carries the link. */
|
|
112
115
|
postTitle: string | null;
|
|
113
116
|
postSlug: string | null;
|
|
114
117
|
actor: AdminCommentActor;
|
|
118
|
+
surface?: CommentSurface;
|
|
119
|
+
/** When the latest verdict, automatic or the owner's, was written. */
|
|
120
|
+
moderatedAt?: string | null;
|
|
121
|
+
updatedAt?: string;
|
|
122
|
+
/** A deleted row the owner can still restore: the last instant it can.
|
|
123
|
+
Null for every other row. */
|
|
124
|
+
restorableUntil?: string | null;
|
|
125
|
+
/** When the owner pinned this root to the top of its post. A pin on a
|
|
126
|
+
row that is no longer published stays set and shows again with it. */
|
|
127
|
+
pinnedAt?: string | null;
|
|
128
|
+
/** When the owner locked replies under this root. */
|
|
129
|
+
lockedAt?: string | null;
|
|
130
|
+
/** The post's page on the public site, as a path: `/blog/<slug>` or
|
|
131
|
+
`/mood/<id>`. Null when the post could not be looked up. */
|
|
132
|
+
postPath?: string | null;
|
|
133
|
+
/** The owner wrote it: the public `CommentAuthor.byAuthor` rule, a row
|
|
134
|
+
written signed in by the reader whose address is the owner's. */
|
|
135
|
+
byAuthor?: boolean;
|
|
115
136
|
}
|
|
116
137
|
/** One page of the moderation queue. */
|
|
117
138
|
export interface AdminCommentListResult {
|
|
@@ -121,6 +142,274 @@ export interface AdminCommentListResult {
|
|
|
121
142
|
/** `offset + comments.length`, or null on the last page. */
|
|
122
143
|
nextOffset: number | null;
|
|
123
144
|
}
|
|
145
|
+
/** The numbers drawn above the queue. */
|
|
146
|
+
export interface AdminCommentSummary {
|
|
147
|
+
byStatus: Record<AdminCommentStatus, number>;
|
|
148
|
+
/** Comments written in the last 24 hours. */
|
|
149
|
+
today: number;
|
|
150
|
+
oldestHeldAt: string | null;
|
|
151
|
+
reasons: Array<{
|
|
152
|
+
reason: string;
|
|
153
|
+
count: number;
|
|
154
|
+
}>;
|
|
155
|
+
/** `slug` and `path` as on AdminCommentRecord's `postSlug` and
|
|
156
|
+
`postPath`; `path` is optional only for the portal's demo api. */
|
|
157
|
+
topPosts: Array<{
|
|
158
|
+
surface: CommentSurface;
|
|
159
|
+
postId: string;
|
|
160
|
+
count: number;
|
|
161
|
+
title: string | null;
|
|
162
|
+
slug: string | null;
|
|
163
|
+
path?: string | null;
|
|
164
|
+
}>;
|
|
165
|
+
daily: Array<{
|
|
166
|
+
date: string;
|
|
167
|
+
count: number;
|
|
168
|
+
}>;
|
|
169
|
+
}
|
|
170
|
+
export type AdminCommentQueueSort = 'newest' | 'oldest' | 'risk';
|
|
171
|
+
/** `akismet` alone, `llm` for Akismet plus the second opinion, `none` for
|
|
172
|
+
rows no model judged. */
|
|
173
|
+
export type AdminCommentModelFilter = 'akismet' | 'llm' | 'none';
|
|
174
|
+
/** The created-at window a queue page was read over. Search and the risk
|
|
175
|
+
sort always read one (30 days unless asked, 90 at most). */
|
|
176
|
+
export interface AdminCommentQueueWindow {
|
|
177
|
+
from: string | null;
|
|
178
|
+
to: string | null;
|
|
179
|
+
/** True when the window asked for was wider than 90 days and was cut to
|
|
180
|
+
the 90 before its end; `from` is then where the read started. */
|
|
181
|
+
clamped?: boolean;
|
|
182
|
+
}
|
|
183
|
+
/** GET /admin/comments. */
|
|
184
|
+
export interface AdminCommentQueueResult extends AdminCommentListResult {
|
|
185
|
+
summary: AdminCommentSummary;
|
|
186
|
+
window: AdminCommentQueueWindow | null;
|
|
187
|
+
}
|
|
188
|
+
export type AdminCommentAction = 'approve' | 'hide' | 'reject' | 'delete' | 'restore';
|
|
189
|
+
export declare const ADMIN_COMMENT_REJECT_REASONS: readonly ['spam', 'promotional', 'abuse', 'off_topic', 'personal_info'];
|
|
190
|
+
export type AdminCommentRejectReason = (typeof ADMIN_COMMENT_REJECT_REASONS)[number];
|
|
191
|
+
/** What one act did to one row. `already_*` is a repeat that changed
|
|
192
|
+
nothing; `not_restorable` is a deleted row past its window or removed in
|
|
193
|
+
a way restore cannot undo; `not_available` is a row gone, or in a state
|
|
194
|
+
the act cannot leave. */
|
|
195
|
+
export type AdminCommentActionResult = 'approved' | 'hidden' | 'rejected' | 'deleted' | 'restored' | 'already_approved' | 'already_hidden' | 'already_rejected' | 'already_deleted' | 'not_restorable' | 'not_available';
|
|
196
|
+
/** POST /admin/comments/:id. `reason` is required for `reject`. */
|
|
197
|
+
export interface AdminCommentActionRequest {
|
|
198
|
+
action: AdminCommentAction;
|
|
199
|
+
reason?: AdminCommentRejectReason;
|
|
200
|
+
}
|
|
201
|
+
export interface AdminCommentActionResponse {
|
|
202
|
+
result: AdminCommentActionResult;
|
|
203
|
+
comment: {
|
|
204
|
+
id: string;
|
|
205
|
+
status: AdminCommentStatus;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** POST /admin/comments/bulk: at most 20 ids, applied as one batch. */
|
|
209
|
+
export interface AdminCommentBulkRequest {
|
|
210
|
+
ids: string[];
|
|
211
|
+
action: AdminCommentAction;
|
|
212
|
+
reason?: AdminCommentRejectReason;
|
|
213
|
+
}
|
|
214
|
+
/** One result per id, in the order sent. `status` is the row's status
|
|
215
|
+
after the call whatever the result, so a `not_available` row says where
|
|
216
|
+
it actually is; it is null only when no row has that id. */
|
|
217
|
+
export interface AdminCommentBulkResponse {
|
|
218
|
+
results: Array<{
|
|
219
|
+
id: string;
|
|
220
|
+
result: AdminCommentActionResult;
|
|
221
|
+
status: AdminCommentStatus | null;
|
|
222
|
+
}>;
|
|
223
|
+
}
|
|
224
|
+
/** POST /admin/comments/:id/reply. */
|
|
225
|
+
export interface AdminCommentReplyRequest {
|
|
226
|
+
body: string;
|
|
227
|
+
/** A UUID made once per reply and sent again on every retry: it becomes
|
|
228
|
+
the reply's id, so a retry answers the first reply instead of posting
|
|
229
|
+
a second. The same id with another body or comment is a 409
|
|
230
|
+
`reply_id_collision`. Omitted, every call posts a new reply. */
|
|
231
|
+
replyId?: string;
|
|
232
|
+
}
|
|
233
|
+
/** `parentId` is the thread root the reply joined. */
|
|
234
|
+
export interface AdminCommentReplyResponse {
|
|
235
|
+
comment: {
|
|
236
|
+
id: string;
|
|
237
|
+
parentId: string;
|
|
238
|
+
status: AdminCommentStatus;
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** The site-wide lockdown: every anonymous comment waits for an email
|
|
242
|
+
until `until`. */
|
|
243
|
+
export interface AdminCommentLockdown {
|
|
244
|
+
reason: string;
|
|
245
|
+
since: string;
|
|
246
|
+
until: string;
|
|
247
|
+
by: 'auto' | 'owner';
|
|
248
|
+
}
|
|
249
|
+
/** POST /admin/comments/lockdown: 1 to 10080 minutes, note up to 200
|
|
250
|
+
characters. */
|
|
251
|
+
export interface AdminCommentLockdownRequest {
|
|
252
|
+
minutes: number;
|
|
253
|
+
note?: string;
|
|
254
|
+
}
|
|
255
|
+
/** GET, POST and DELETE /admin/comments/lockdown. */
|
|
256
|
+
export interface AdminCommentLockdownResponse {
|
|
257
|
+
lockdown: AdminCommentLockdown | null;
|
|
258
|
+
}
|
|
259
|
+
/** A site-wide mode: every post at least this strict. */
|
|
260
|
+
export type AdminCommentSiteMode = Exclude<CommentsMode, 'open'>;
|
|
261
|
+
/** The owner's standing site-wide comment switches, on top of every post's
|
|
262
|
+
own mode (tags, then the portal's per-post override). Neither expires. */
|
|
263
|
+
export interface AdminCommentSitePolicy {
|
|
264
|
+
/** Null: each post's own mode decides. Otherwise every post, on every
|
|
265
|
+
surface, is at least this strict (open < readonly < off); a post that
|
|
266
|
+
is already stricter stays so. */
|
|
267
|
+
mode: AdminCommentSiteMode | null;
|
|
268
|
+
/** When `mode` was last set; null while it is null. */
|
|
269
|
+
modeSince: string | null;
|
|
270
|
+
/** Every anonymous comment waits for its writer to confirm an email, as
|
|
271
|
+
in a lockdown, until switched off. A writer who gives no address is
|
|
272
|
+
asked for one. Signed-in readers post as usual. */
|
|
273
|
+
requireEmail: boolean;
|
|
274
|
+
/** When `requireEmail` was turned on; null while it is off. */
|
|
275
|
+
requireEmailSince: string | null;
|
|
276
|
+
}
|
|
277
|
+
/** PUT /admin/comments/site-policy: the fields to change, at least one. */
|
|
278
|
+
export interface AdminCommentSitePolicyRequest {
|
|
279
|
+
mode?: AdminCommentSiteMode | null;
|
|
280
|
+
requireEmail?: boolean;
|
|
281
|
+
}
|
|
282
|
+
/** GET and PUT /admin/comments/site-policy. */
|
|
283
|
+
export interface AdminCommentSitePolicyResponse {
|
|
284
|
+
policy: AdminCommentSitePolicy;
|
|
285
|
+
}
|
|
286
|
+
/** PUT and DELETE /admin/comments/:id/pin. `replaced` is the comment the
|
|
287
|
+
PUT took the post's pin from, or null; always null on DELETE. */
|
|
288
|
+
export interface AdminCommentPinResponse {
|
|
289
|
+
comment: {
|
|
290
|
+
id: string;
|
|
291
|
+
surface: CommentSurface;
|
|
292
|
+
postId: string;
|
|
293
|
+
pinnedAt: string | null;
|
|
294
|
+
};
|
|
295
|
+
replaced: string | null;
|
|
296
|
+
}
|
|
297
|
+
/** PUT and DELETE /admin/comments/:id/lock. `comment` is the thread root,
|
|
298
|
+
which is not the id in the path when that was a reply. */
|
|
299
|
+
export interface AdminCommentLockResponse {
|
|
300
|
+
comment: {
|
|
301
|
+
id: string;
|
|
302
|
+
surface: CommentSurface;
|
|
303
|
+
postId: string;
|
|
304
|
+
lockedAt: string | null;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** One post's comment mode as the portal shows it: the portal's override
|
|
308
|
+
beside what the post's tags give (the site default for a mood post). */
|
|
309
|
+
export interface AdminCommentModeState {
|
|
310
|
+
surface: CommentSurface;
|
|
311
|
+
postId: string;
|
|
312
|
+
/** Null when the tags decide. */
|
|
313
|
+
override: CommentsMode | null;
|
|
314
|
+
/** Null when the post could not be looked up. */
|
|
315
|
+
tagMode: CommentsMode | null;
|
|
316
|
+
/** What readers get: `override`, else `tagMode`. */
|
|
317
|
+
effectiveMode: CommentsMode | null;
|
|
318
|
+
/** When the override was last set; null without one. */
|
|
319
|
+
updatedAt: string | null;
|
|
320
|
+
title: string | null;
|
|
321
|
+
/** Blog only; null for a mood post, which has no slug. */
|
|
322
|
+
slug: string | null;
|
|
323
|
+
/** The post's page as a path, `/blog/<slug>` or `/mood/<id>`; null when
|
|
324
|
+
the post could not be looked up. Optional only for the portal's demo
|
|
325
|
+
api; site-api sends it. */
|
|
326
|
+
path?: string | null;
|
|
327
|
+
}
|
|
328
|
+
/** GET /admin/comment-modes: every overridden post. */
|
|
329
|
+
export interface AdminCommentModeListResult {
|
|
330
|
+
modes: AdminCommentModeState[];
|
|
331
|
+
}
|
|
332
|
+
/** PUT /admin/comment-modes/:surface/:postId. */
|
|
333
|
+
export interface AdminCommentModeRequest {
|
|
334
|
+
mode: CommentsMode;
|
|
335
|
+
}
|
|
336
|
+
/** GET, PUT and DELETE /admin/comment-modes/:surface/:postId. */
|
|
337
|
+
export interface AdminCommentModeResponse {
|
|
338
|
+
mode: AdminCommentModeState;
|
|
339
|
+
}
|
|
340
|
+
/** One message sent through /message, as the owner's inbox shows it. The
|
|
341
|
+
body is private: it never leaves the admin routes. */
|
|
342
|
+
export interface AdminOwnerMessage {
|
|
343
|
+
id: string;
|
|
344
|
+
state: MessageState;
|
|
345
|
+
displayName: string;
|
|
346
|
+
body: string;
|
|
347
|
+
locale: MessageLocale;
|
|
348
|
+
/** The reader the address resolves to. It proves who wrote the message
|
|
349
|
+
only when `authAtWrite` is `verified`. */
|
|
350
|
+
readerId: string | null;
|
|
351
|
+
/** `verified` when a signed-in reader's session sent it; `anonymous` when
|
|
352
|
+
the address was typed. Missing on older responses means unknown. */
|
|
353
|
+
authAtWrite?: CommentAuthAtWrite;
|
|
354
|
+
/** Null when the sender left no address. */
|
|
355
|
+
emailHash: string | null;
|
|
356
|
+
/** Why the risk stack filed it as spam, and which model said so. */
|
|
357
|
+
spamNote: string | null;
|
|
358
|
+
spamModel: string | null;
|
|
359
|
+
repliedAt: string | null;
|
|
360
|
+
country: string | null;
|
|
361
|
+
createdAt: string;
|
|
362
|
+
updatedAt: string;
|
|
363
|
+
}
|
|
364
|
+
/** GET /admin/messages `state`: one state, or `inbox` for new, read and
|
|
365
|
+
replied together -- everything not archived or spam. */
|
|
366
|
+
export type AdminOwnerMessageFilter = MessageState | 'inbox';
|
|
367
|
+
/** GET /admin/messages. `counts` covers the whole inbox whatever the
|
|
368
|
+
filter; `total` is the filtered count. */
|
|
369
|
+
export interface AdminOwnerMessageListResult {
|
|
370
|
+
messages: AdminOwnerMessage[];
|
|
371
|
+
counts: Record<MessageState, number>;
|
|
372
|
+
total: number;
|
|
373
|
+
nextOffset: number | null;
|
|
374
|
+
}
|
|
375
|
+
/** Whether a reply would reach the sender, and if not, why. */
|
|
376
|
+
export type AdminOwnerMessageReplyability = 'ok' | 'no_address' | 'unverified' | 'suppressed';
|
|
377
|
+
/** GET /admin/messages/:id. `history` is what else the same address sent,
|
|
378
|
+
newest first; `sender.email` is set only when a reply can reach it. */
|
|
379
|
+
export interface AdminOwnerMessageDetail {
|
|
380
|
+
message: AdminOwnerMessage;
|
|
381
|
+
sender: {
|
|
382
|
+
replyable: AdminOwnerMessageReplyability;
|
|
383
|
+
email: string | null;
|
|
384
|
+
readerId: string | null;
|
|
385
|
+
};
|
|
386
|
+
history: AdminOwnerMessage[];
|
|
387
|
+
/** The sender's keys and signals, in the comment writer's shape; `cluster`
|
|
388
|
+
counts comments, reactions and messages. Absent from a site-api that
|
|
389
|
+
predates it. */
|
|
390
|
+
actor?: AdminCommentActor;
|
|
391
|
+
}
|
|
392
|
+
export type AdminOwnerMessageAction = 'read' | 'archive' | 'unarchive' | 'spam' | 'unspam';
|
|
393
|
+
/** POST /admin/messages/:id. */
|
|
394
|
+
export interface AdminOwnerMessageActionRequest {
|
|
395
|
+
action: AdminOwnerMessageAction;
|
|
396
|
+
}
|
|
397
|
+
/** `changed` is false when the message was already past the act; `message`
|
|
398
|
+
is then what it is now. */
|
|
399
|
+
export interface AdminOwnerMessageActionResponse {
|
|
400
|
+
message: AdminOwnerMessage;
|
|
401
|
+
changed: boolean;
|
|
402
|
+
}
|
|
403
|
+
/** POST /admin/messages/:id/reply: 2 to 4000 characters. */
|
|
404
|
+
export interface AdminOwnerMessageReplyRequest {
|
|
405
|
+
body: string;
|
|
406
|
+
}
|
|
407
|
+
/** `recipientEmail` is masked. */
|
|
408
|
+
export interface AdminOwnerMessageReplyResponse {
|
|
409
|
+
message: AdminOwnerMessage;
|
|
410
|
+
recipientName: string;
|
|
411
|
+
recipientEmail: string;
|
|
412
|
+
}
|
|
124
413
|
/** What a ban can hold onto. Values are hashes (or the ASN as text) for
|
|
125
414
|
every kind but the two domain kinds, which are stored raw -- a domain is
|
|
126
415
|
not personal data. `client_fp` matches either the exact or the stable
|
|
@@ -205,6 +494,8 @@ export interface AdminClusterCount {
|
|
|
205
494
|
comments: number;
|
|
206
495
|
held: number;
|
|
207
496
|
reactions: number;
|
|
497
|
+
/** Owner messages sharing the key. Absent from a site-api that predates it. */
|
|
498
|
+
messages?: number;
|
|
208
499
|
}
|
|
209
500
|
/** Where a row came from, as far as the write path could tell. Shared by
|
|
210
501
|
comments and reactions; a reaction's block has no email, no link domains
|
|
@@ -293,8 +584,21 @@ export interface AdminBanInput {
|
|
|
293
584
|
note?: string;
|
|
294
585
|
expiresAt?: string | null;
|
|
295
586
|
purge?: boolean;
|
|
587
|
+
/** Keys a purge also matches without banning them, such as the writer's
|
|
588
|
+
device fingerprint. Ignored unless `purge` is set. */
|
|
589
|
+
sweepKeys?: Array<{
|
|
590
|
+
type: AdminBanKeyType;
|
|
591
|
+
value: string;
|
|
592
|
+
}>;
|
|
593
|
+
/** The comment the ban was raised from. It is removed in the same
|
|
594
|
+
restorable operation whatever `purge` says, and its writer is the one
|
|
595
|
+
signed-in reader a purge may remove published comments from. */
|
|
596
|
+
removeCommentId?: string | null;
|
|
296
597
|
revokeReaderId?: string | null;
|
|
297
598
|
}
|
|
599
|
+
/** `POST /admin/bans/preview`: the scope `POST /admin/bans` would reach with
|
|
600
|
+
the same fields. */
|
|
601
|
+
export type AdminBanPreviewInput = Pick<AdminBanInput, 'keys' | 'revokeReaderId' | 'purge' | 'sweepKeys' | 'removeCommentId'>;
|
|
298
602
|
export interface AdminBanResult {
|
|
299
603
|
bans: AdminBan[];
|
|
300
604
|
purged: {
|
|
@@ -308,10 +612,21 @@ export interface AdminBanPreview {
|
|
|
308
612
|
sessions: number;
|
|
309
613
|
comments: Record<AdminCommentStatus | 'total', number>;
|
|
310
614
|
reactions: number;
|
|
615
|
+
/** What the ban would really remove. The optional fields are absent from
|
|
616
|
+
a site-api that predates them. */
|
|
311
617
|
purge: {
|
|
312
618
|
comments: number;
|
|
313
619
|
reactions: number;
|
|
620
|
+
/** Of `comments`, the ones readers can see now. */
|
|
621
|
+
published?: number;
|
|
622
|
+
/** Distinct sessions the removed rows came from. */
|
|
623
|
+
sessions?: number;
|
|
624
|
+
/** Reader accounts, other than the target comment's own, that lose a row. */
|
|
625
|
+
otherAccounts?: number;
|
|
314
626
|
};
|
|
627
|
+
/** Published comments a purge leaves alone because another signed-in
|
|
628
|
+
reader wrote them. Absent from a site-api that predates the rule. */
|
|
629
|
+
spared?: number;
|
|
315
630
|
windowDays: 90;
|
|
316
631
|
purgeLimit: number;
|
|
317
632
|
purgeAllowed: boolean;
|
|
@@ -346,8 +661,13 @@ export interface AdminBanRestoreResult {
|
|
|
346
661
|
export interface AdminBanListResult {
|
|
347
662
|
bans: AdminBan[];
|
|
348
663
|
}
|
|
664
|
+
/** A reader a ban revoked -- `GET /admin/readers/revoked`. `emailHash` is
|
|
665
|
+
the value of an `email` ban key, for finding the key bans that outlive
|
|
666
|
+
a restore. `updatedAt` is when the row last changed: the revocation,
|
|
667
|
+
unless something touched the reader since. */
|
|
349
668
|
export interface AdminBannedReader {
|
|
350
669
|
readerId: string;
|
|
670
|
+
emailHash: string;
|
|
351
671
|
email: string;
|
|
352
672
|
displayName: string | null;
|
|
353
673
|
updatedAt: string;
|
|
@@ -355,8 +675,31 @@ export interface AdminBannedReader {
|
|
|
355
675
|
export interface AdminBannedReaderListResult {
|
|
356
676
|
readers: AdminBannedReader[];
|
|
357
677
|
}
|
|
678
|
+
/** POST /admin/readers/:readerId/restore. */
|
|
679
|
+
export interface AdminReaderRestoreResult {
|
|
680
|
+
readerId: string;
|
|
681
|
+
restoredAt: string;
|
|
682
|
+
}
|
|
358
683
|
/** Everything about one key in one response --
|
|
359
684
|
`GET /admin/sources/:type/:value`. */
|
|
685
|
+
export interface AdminSourceAddress {
|
|
686
|
+
emailHash: string;
|
|
687
|
+
/** Plaintext while a row still holds it. */
|
|
688
|
+
email: string | null;
|
|
689
|
+
confirmed: boolean;
|
|
690
|
+
comments: number;
|
|
691
|
+
messages: number;
|
|
692
|
+
lastSeenAt: string;
|
|
693
|
+
}
|
|
694
|
+
export interface AdminSourceDevice {
|
|
695
|
+
clientFp: string;
|
|
696
|
+
browser: string | null;
|
|
697
|
+
os: string | null;
|
|
698
|
+
comments: number;
|
|
699
|
+
reactions: number;
|
|
700
|
+
messages: number;
|
|
701
|
+
lastSeenAt: string;
|
|
702
|
+
}
|
|
360
703
|
export interface AdminSourceProfile {
|
|
361
704
|
key: {
|
|
362
705
|
type: AdminSourceKeyType;
|
|
@@ -406,6 +749,19 @@ export interface AdminSourceProfile {
|
|
|
406
749
|
held: number;
|
|
407
750
|
reactions: number;
|
|
408
751
|
};
|
|
752
|
+
/** Owner messages under this key. Absent from a site-api that predates it. */
|
|
753
|
+
messages?: {
|
|
754
|
+
total: number;
|
|
755
|
+
byState: Record<MessageState, number>;
|
|
756
|
+
/** Newest 50. */
|
|
757
|
+
rows: AdminOwnerMessage[];
|
|
758
|
+
};
|
|
759
|
+
/** Addresses written under this key, newest first, at most 10. `confirmed`
|
|
760
|
+
means a signed-in reader wrote with it; a typed address is evidence of
|
|
761
|
+
what this key claimed, never a link to that reader. */
|
|
762
|
+
addresses?: AdminSourceAddress[];
|
|
763
|
+
/** Device fingerprints seen under this key, newest first, at most 10. */
|
|
764
|
+
devices?: AdminSourceDevice[];
|
|
409
765
|
/** Writes per hour over the source's last 7 days. */
|
|
410
766
|
hourly: Array<{
|
|
411
767
|
hour: string;
|
|
@@ -640,3 +996,46 @@ export interface AdminReactionInsights {
|
|
|
640
996
|
sessions: number;
|
|
641
997
|
}>;
|
|
642
998
|
}
|
|
999
|
+
/** Reject and restore are logged as `comment.moderate`, with the status
|
|
1000
|
+
they left the row in. */
|
|
1001
|
+
export declare const ADMIN_ACTIVITY_EVENTS: readonly ['comment.create', 'comment.edit', 'comment.remove', 'comment.moderate', 'comment.approve', 'comment.hide', 'comment.delete', 'reaction.add', 'reaction.remove'];
|
|
1002
|
+
export type AdminActivityEvent = (typeof ADMIN_ACTIVITY_EVENTS)[number];
|
|
1003
|
+
export type AdminActivityActor = 'reader' | 'model' | 'owner';
|
|
1004
|
+
export type AdminActivitySource = 'web' | 'portal' | 'telegram' | 'cron';
|
|
1005
|
+
export type AdminActivityTargetType = 'comment' | 'post';
|
|
1006
|
+
export type AdminActivityFamily = 'comments' | 'reactions';
|
|
1007
|
+
export interface AdminActivityRecord {
|
|
1008
|
+
id: string;
|
|
1009
|
+
createdAt: string;
|
|
1010
|
+
event: AdminActivityEvent;
|
|
1011
|
+
actor: AdminActivityActor;
|
|
1012
|
+
source: AdminActivitySource;
|
|
1013
|
+
targetType: AdminActivityTargetType;
|
|
1014
|
+
targetId: string;
|
|
1015
|
+
postId: string | null;
|
|
1016
|
+
postTitle: string | null;
|
|
1017
|
+
postSlug: string | null;
|
|
1018
|
+
displayName: string | null;
|
|
1019
|
+
readerId: string | null;
|
|
1020
|
+
anonymous: boolean;
|
|
1021
|
+
emoji: string | null;
|
|
1022
|
+
status: string | null;
|
|
1023
|
+
reason: string | null;
|
|
1024
|
+
note: string | null;
|
|
1025
|
+
}
|
|
1026
|
+
export interface AdminActivitySummary {
|
|
1027
|
+
byEvent: Record<AdminActivityEvent, number>;
|
|
1028
|
+
today: number;
|
|
1029
|
+
reactionsNet: number;
|
|
1030
|
+
daily: Array<{
|
|
1031
|
+
date: string;
|
|
1032
|
+
comments: number;
|
|
1033
|
+
reactions: number;
|
|
1034
|
+
}>;
|
|
1035
|
+
}
|
|
1036
|
+
export interface AdminActivityListResult {
|
|
1037
|
+
summary: AdminActivitySummary;
|
|
1038
|
+
entries: AdminActivityRecord[];
|
|
1039
|
+
total: number;
|
|
1040
|
+
nextOffset: number | null;
|
|
1041
|
+
}
|
package/dist/admin.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/admin.ts
|
|
2
|
+
var ADMIN_COMMENT_REJECT_REASONS = ["spam", "promotional", "abuse", "off_topic", "personal_info"];
|
|
3
|
+
var ADMIN_ACTIVITY_EVENTS = [
|
|
4
|
+
"comment.create",
|
|
5
|
+
"comment.edit",
|
|
6
|
+
"comment.remove",
|
|
7
|
+
"comment.moderate",
|
|
8
|
+
"comment.approve",
|
|
9
|
+
"comment.hide",
|
|
10
|
+
"comment.delete",
|
|
11
|
+
"reaction.add",
|
|
12
|
+
"reaction.remove"
|
|
13
|
+
];
|
|
14
|
+
export {
|
|
15
|
+
ADMIN_ACTIVITY_EVENTS,
|
|
16
|
+
ADMIN_COMMENT_REJECT_REASONS
|
|
17
|
+
};
|
package/dist/comments.d.ts
CHANGED
|
@@ -348,14 +348,31 @@ export interface Comment {
|
|
|
348
348
|
/** Soft-deleted but kept as a shape-preserving placeholder because a reply
|
|
349
349
|
hangs underneath it. `body`/`author` are empty on a tombstone. */
|
|
350
350
|
tombstone: boolean;
|
|
351
|
+
/** The owner pinned this root above the thread. At most one per post;
|
|
352
|
+
absent on every other row, replies included, and on every row from a
|
|
353
|
+
server that predates pins. */
|
|
354
|
+
pinned?: boolean;
|
|
355
|
+
/** The owner closed this root's thread to new replies; a reply under it
|
|
356
|
+
is refused with `403 thread_locked`. Set on the root only; absent
|
|
357
|
+
means open. */
|
|
358
|
+
locked?: boolean;
|
|
351
359
|
}
|
|
352
360
|
export interface CommentListResult {
|
|
361
|
+
/** The page's roots, newest first, then their replies. The first page
|
|
362
|
+
(no `before`) puts the post's pinned root ahead of the other roots when
|
|
363
|
+
it has one, on top of `limit` roots; no later page repeats it. */
|
|
353
364
|
comments: Comment[];
|
|
354
365
|
hasMore: boolean;
|
|
355
366
|
/** Cursor for the next page, or null when `hasMore` is false. */
|
|
356
367
|
nextBefore: string | null;
|
|
357
368
|
/** Published comments on the post (excludes held/rejected/deleted). */
|
|
358
369
|
total: number;
|
|
370
|
+
/** The post's policy when the owner overrode its mode in the portal or a
|
|
371
|
+
site-wide switch applies, on the first page only. Absent means the post
|
|
372
|
+
follows its tags, which the page was drawn from -- nothing to
|
|
373
|
+
reconcile. The page acts on `mode`, and on `requireVerifiedEmail` by
|
|
374
|
+
drawing the address as required. */
|
|
375
|
+
policy?: CommentPolicy;
|
|
359
376
|
}
|
|
360
377
|
export interface CommentCreateInput {
|
|
361
378
|
/** Defaults to `blog`. `mood` requires the post to have a linked
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,19 @@ var BLOG_ANALYTICS_EVENTS_DEFAULT_LIMIT = 50;
|
|
|
10
10
|
var BLOG_ANALYTICS_READ_THRESHOLD_MS = 5000;
|
|
11
11
|
var BLOG_ANALYTICS_COMPLETION_SCROLL_DEPTH = 0.9;
|
|
12
12
|
var BLOG_ANALYTICS_RANGE_OPTIONS = [7, 30, 90];
|
|
13
|
+
// src/admin.ts
|
|
14
|
+
var ADMIN_COMMENT_REJECT_REASONS = ["spam", "promotional", "abuse", "off_topic", "personal_info"];
|
|
15
|
+
var ADMIN_ACTIVITY_EVENTS = [
|
|
16
|
+
"comment.create",
|
|
17
|
+
"comment.edit",
|
|
18
|
+
"comment.remove",
|
|
19
|
+
"comment.moderate",
|
|
20
|
+
"comment.approve",
|
|
21
|
+
"comment.hide",
|
|
22
|
+
"comment.delete",
|
|
23
|
+
"reaction.add",
|
|
24
|
+
"reaction.remove"
|
|
25
|
+
];
|
|
13
26
|
// src/comments.ts
|
|
14
27
|
var READER_PROVIDERS = ["email", "github", "google"];
|
|
15
28
|
var COMMENT_LOCALES = ["zh", "en"];
|
|
@@ -218,7 +231,9 @@ var TELEGRAM_OPS_BROADCAST_STATUSES = [
|
|
|
218
231
|
"failed"
|
|
219
232
|
];
|
|
220
233
|
export {
|
|
234
|
+
ADMIN_ACTIVITY_EVENTS,
|
|
221
235
|
ADMIN_BASE_PATH,
|
|
236
|
+
ADMIN_COMMENT_REJECT_REASONS,
|
|
222
237
|
API_PREFIX,
|
|
223
238
|
AVATAR_CLASSES,
|
|
224
239
|
AVATAR_OFFER_SIZE,
|
package/dist/messages.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* reader identity, and the ops-bot notification. See site-api's
|
|
13
13
|
* features/messages/server/message-service.ts.
|
|
14
14
|
*/
|
|
15
|
+
import type { ClientEvidence } from './comments';
|
|
15
16
|
export declare const MESSAGE_LOCALES: readonly ['zh', 'en'];
|
|
16
17
|
export type MessageLocale = (typeof MESSAGE_LOCALES)[number];
|
|
17
18
|
/** Body bounds, shared by the form's client-side validation and the service. */
|
|
@@ -31,7 +32,9 @@ export declare const MESSAGE_MAX_NAME_LENGTH = 32;
|
|
|
31
32
|
*/
|
|
32
33
|
export declare const MESSAGE_STATES: readonly ['new', 'read', 'replied', 'archived', 'spam'];
|
|
33
34
|
export type MessageState = (typeof MESSAGE_STATES)[number];
|
|
34
|
-
|
|
35
|
+
/** The fingerprint fields are the comment box's own evidence
|
|
36
|
+
(`ClientEvidence`): optional, bounded, and never a gate. */
|
|
37
|
+
export interface OwnerMessageCreateInput extends ClientEvidence {
|
|
35
38
|
/** Plain text, MESSAGE_MIN_BODY_LENGTH–MESSAGE_MAX_BODY_LENGTH characters. */
|
|
36
39
|
body: string;
|
|
37
40
|
displayName: string;
|