@bunizao/contracts 0.4.0 → 0.5.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/comments.d.ts +27 -1
- package/dist/comments.js +12 -0
- package/dist/content.d.ts +11 -0
- package/dist/index.js +21 -0
- package/dist/messages.d.ts +6 -5
- package/dist/mood.d.ts +25 -0
- package/dist/mood.js +2 -0
- package/dist/telegram-ops.d.ts +15 -0
- package/dist/telegram-ops.js +7 -0
- package/package.json +1 -1
package/dist/comments.d.ts
CHANGED
|
@@ -21,6 +21,25 @@ export declare const READER_GRADES: readonly ['l0', 'l1', 'l2'];
|
|
|
21
21
|
export type ReaderGrade = (typeof READER_GRADES)[number];
|
|
22
22
|
export declare const COMMENT_STATUSES: readonly ['published', 'held', 'rejected', 'deleted'];
|
|
23
23
|
export type CommentStatus = (typeof COMMENT_STATUSES)[number];
|
|
24
|
+
/** Where a comment was written. `blog` rows key on Ghost post ids; `mood`
|
|
25
|
+
rows key on the Telegram channel message id and are bridged into the
|
|
26
|
+
channel's discussion group by the ops bot — plans/mood-comments-bridge.md. */
|
|
27
|
+
export declare const COMMENT_SURFACES: readonly ['blog', 'mood'];
|
|
28
|
+
export type CommentSurface = (typeof COMMENT_SURFACES)[number];
|
|
29
|
+
/** Length of the anchor token derived from a comment id. */
|
|
30
|
+
export declare const COMMENT_ANCHOR_TOKEN_LENGTH = 12;
|
|
31
|
+
/**
|
|
32
|
+
* Stable, unguessable-enough anchor for a comment: the first 12 hex chars of
|
|
33
|
+
* sha256(commentId). It rides in the bridged Telegram message's link
|
|
34
|
+
* (`/mood/<postId>#c-<token>`), in the owner card's View URL, and as the
|
|
35
|
+
* `id="c-<token>"` of the rendered row. The read path matches bridged
|
|
36
|
+
* messages back to their rows by this string, so it must be computed the
|
|
37
|
+
* same way everywhere — hence one helper here. Async because it uses Web
|
|
38
|
+
* Crypto, which is what Workers, Node >= 20, and browsers share.
|
|
39
|
+
*/
|
|
40
|
+
export declare function commentAnchorToken(commentId: string): Promise<string>;
|
|
41
|
+
/** Matches `#c-<token>` anywhere in a URL or text; group 1 is the token. */
|
|
42
|
+
export declare const COMMENT_ANCHOR_PATTERN: RegExp;
|
|
24
43
|
export declare const MODERATION_ACTIONS: readonly ['publish', 'hold', 'reject', 'unsure'];
|
|
25
44
|
export type ModerationAction = (typeof MODERATION_ACTIONS)[number];
|
|
26
45
|
export declare const MODERATION_REASONS: readonly ['ok', 'spam', 'promotional', 'abuse', 'off_topic', 'personal_info'];
|
|
@@ -154,8 +173,12 @@ export interface CommentAuthor {
|
|
|
154
173
|
}
|
|
155
174
|
export interface Comment {
|
|
156
175
|
id: string;
|
|
157
|
-
|
|
176
|
+
surface: CommentSurface;
|
|
177
|
+
/** `blog`: Ghost's post.id, stable across slug renames. `mood`: the
|
|
178
|
+
Telegram channel message id, the same value `/mood/[id]` routes on. */
|
|
158
179
|
postId: string;
|
|
180
|
+
/** `commentAnchorToken(id)`, precomputed so clients never hash. */
|
|
181
|
+
anchorToken: string;
|
|
159
182
|
/** Always a root comment id, or null. Threading is one level deep. */
|
|
160
183
|
parentId: string | null;
|
|
161
184
|
author: CommentAuthor;
|
|
@@ -189,6 +212,9 @@ export interface CommentListResult {
|
|
|
189
212
|
total: number;
|
|
190
213
|
}
|
|
191
214
|
export interface CommentCreateInput {
|
|
215
|
+
/** Defaults to `blog`. `mood` requires the post to have a linked
|
|
216
|
+
discussion thread (`MoodContentDocument.discussionLinked`). */
|
|
217
|
+
surface?: CommentSurface;
|
|
192
218
|
postId: string;
|
|
193
219
|
body: string;
|
|
194
220
|
/** Root comment id this replies to. Omitted or null for a root comment. */
|
package/dist/comments.js
CHANGED
|
@@ -3,6 +3,14 @@ var READER_PROVIDERS = ["email", "github", "google"];
|
|
|
3
3
|
var COMMENT_LOCALES = ["zh", "en"];
|
|
4
4
|
var READER_GRADES = ["l0", "l1", "l2"];
|
|
5
5
|
var COMMENT_STATUSES = ["published", "held", "rejected", "deleted"];
|
|
6
|
+
var COMMENT_SURFACES = ["blog", "mood"];
|
|
7
|
+
var COMMENT_ANCHOR_TOKEN_LENGTH = 12;
|
|
8
|
+
async function commentAnchorToken(commentId) {
|
|
9
|
+
const bytes = new TextEncoder().encode(commentId);
|
|
10
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
11
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, COMMENT_ANCHOR_TOKEN_LENGTH);
|
|
12
|
+
}
|
|
13
|
+
var COMMENT_ANCHOR_PATTERN = /#c-([0-9a-f]{12})\b/;
|
|
6
14
|
var MODERATION_ACTIONS = ["publish", "hold", "reject", "unsure"];
|
|
7
15
|
var MODERATION_REASONS = [
|
|
8
16
|
"ok",
|
|
@@ -54,10 +62,13 @@ function commentPolicyFromTags(tags, base = DEFAULT_COMMENT_POLICY) {
|
|
|
54
62
|
var acceptsComments = (policy) => policy.mode === "open";
|
|
55
63
|
export {
|
|
56
64
|
COMMENTS_MODES,
|
|
65
|
+
COMMENT_ANCHOR_PATTERN,
|
|
66
|
+
COMMENT_ANCHOR_TOKEN_LENGTH,
|
|
57
67
|
COMMENT_CREATE_OUTCOMES,
|
|
58
68
|
COMMENT_LOCALES,
|
|
59
69
|
COMMENT_POLICY_TAGS,
|
|
60
70
|
COMMENT_STATUSES,
|
|
71
|
+
COMMENT_SURFACES,
|
|
61
72
|
DEFAULT_COMMENT_POLICY,
|
|
62
73
|
DEFAULT_REACTION_EMOJI,
|
|
63
74
|
MODERATION_ACTIONS,
|
|
@@ -68,5 +79,6 @@ export {
|
|
|
68
79
|
READER_PROVIDERS,
|
|
69
80
|
READER_VERIFY_OUTCOMES,
|
|
70
81
|
acceptsComments,
|
|
82
|
+
commentAnchorToken,
|
|
71
83
|
commentPolicyFromTags
|
|
72
84
|
};
|
package/dist/content.d.ts
CHANGED
|
@@ -79,6 +79,17 @@ export interface MoodContentDocument extends ContentDocument {
|
|
|
79
79
|
source: 'mood';
|
|
80
80
|
groupIds?: string[];
|
|
81
81
|
channel?: ContentChannelSummary;
|
|
82
|
+
/** True when the post's copy in the Telegram discussion group is known
|
|
83
|
+
(`mood_posts.discussion_message_id`) and mood comments are enabled, so
|
|
84
|
+
the compose box can post into the thread. False or absent: the page
|
|
85
|
+
keeps the "Leave a comment on Telegram" link instead. */
|
|
86
|
+
discussionLinked?: boolean;
|
|
87
|
+
/** True once the read path has verified, from live traffic, that the
|
|
88
|
+
embed's comment ids are the group's message ids, which is what a
|
|
89
|
+
web reply to a Telegram-origin comment needs to thread correctly.
|
|
90
|
+
Gates reply-to on `telegram` items only; replies to `web` items are
|
|
91
|
+
always allowed. */
|
|
92
|
+
discussionRepliesEnabled?: boolean;
|
|
82
93
|
}
|
|
83
94
|
export interface PostContentDocument extends ContentDocument {
|
|
84
95
|
source: 'post';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,14 @@ var READER_PROVIDERS = ["email", "github", "google"];
|
|
|
16
16
|
var COMMENT_LOCALES = ["zh", "en"];
|
|
17
17
|
var READER_GRADES = ["l0", "l1", "l2"];
|
|
18
18
|
var COMMENT_STATUSES = ["published", "held", "rejected", "deleted"];
|
|
19
|
+
var COMMENT_SURFACES = ["blog", "mood"];
|
|
20
|
+
var COMMENT_ANCHOR_TOKEN_LENGTH = 12;
|
|
21
|
+
async function commentAnchorToken(commentId) {
|
|
22
|
+
const bytes = new TextEncoder().encode(commentId);
|
|
23
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
24
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, COMMENT_ANCHOR_TOKEN_LENGTH);
|
|
25
|
+
}
|
|
26
|
+
var COMMENT_ANCHOR_PATTERN = /#c-([0-9a-f]{12})\b/;
|
|
19
27
|
var MODERATION_ACTIONS = ["publish", "hold", "reject", "unsure"];
|
|
20
28
|
var MODERATION_REASONS = [
|
|
21
29
|
"ok",
|
|
@@ -95,6 +103,7 @@ var MESSAGE_STATES = ["new", "read", "replied", "archived", "spam"];
|
|
|
95
103
|
// src/mood.ts
|
|
96
104
|
var MOOD_SENTIMENT_LABELS = ["joy", "calm", "melancholy", "anger", "anxiety", "neutral"];
|
|
97
105
|
var MOOD_AI_MODELS = ["gpt-5.5", "gpt-5", "claude-sonnet-4.6"];
|
|
106
|
+
var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
|
|
98
107
|
|
|
99
108
|
// src/notify.ts
|
|
100
109
|
var NOTIFY_CHANNELS = ["mood", "blog", "privacy", "announcement"];
|
|
@@ -141,6 +150,12 @@ var LEGACY_HEALTH_PATH = "/v2/health";
|
|
|
141
150
|
|
|
142
151
|
// src/telegram-ops.ts
|
|
143
152
|
var TELEGRAM_OPS_WEBHOOK_PATH = "/webhooks/telegram-ops";
|
|
153
|
+
var TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES = {
|
|
154
|
+
reply: "comment:reply:",
|
|
155
|
+
approve: "comment:approve:",
|
|
156
|
+
hide: "comment:hide:",
|
|
157
|
+
delete: "comment:delete:"
|
|
158
|
+
};
|
|
144
159
|
var NOTIFY_GATE_PATH = "/admin/notify-gate";
|
|
145
160
|
var NOTIFY_GATE_RELEASE_PATH = `${NOTIFY_GATE_PATH}/release`;
|
|
146
161
|
var NOTIFY_GATE_STATES = ["open", "held"];
|
|
@@ -202,11 +217,14 @@ export {
|
|
|
202
217
|
BLOG_ANALYTICS_SUMMARY_ENDPOINT,
|
|
203
218
|
COMMENTS_MODES,
|
|
204
219
|
COMMENTS_PATH,
|
|
220
|
+
COMMENT_ANCHOR_PATTERN,
|
|
221
|
+
COMMENT_ANCHOR_TOKEN_LENGTH,
|
|
205
222
|
COMMENT_CREATE_OUTCOMES,
|
|
206
223
|
COMMENT_LOCALES,
|
|
207
224
|
COMMENT_PATH_PREFIX,
|
|
208
225
|
COMMENT_POLICY_TAGS,
|
|
209
226
|
COMMENT_STATUSES,
|
|
227
|
+
COMMENT_SURFACES,
|
|
210
228
|
CONTENT_DOCUMENT_SOURCES,
|
|
211
229
|
DEFAULT_COMMENT_POLICY,
|
|
212
230
|
DEFAULT_REACTION_EMOJI,
|
|
@@ -230,6 +248,7 @@ export {
|
|
|
230
248
|
MOOD_AI_MODELS,
|
|
231
249
|
MOOD_ARCHIVE_FEED_PATH,
|
|
232
250
|
MOOD_ARCHIVE_STATS_PATH,
|
|
251
|
+
MOOD_COMMENT_ORIGINS,
|
|
233
252
|
MOOD_IMAGE_PROXY_BASE_PATH,
|
|
234
253
|
MOOD_LIVE_COUNTS_PATH,
|
|
235
254
|
MOOD_LIVE_FEED_PATH,
|
|
@@ -273,6 +292,7 @@ export {
|
|
|
273
292
|
TELEGRAM_OPS_BROADCAST_PREVIEW_PATH,
|
|
274
293
|
TELEGRAM_OPS_BROADCAST_SEND_PATH,
|
|
275
294
|
TELEGRAM_OPS_BROADCAST_STATUSES,
|
|
295
|
+
TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES,
|
|
276
296
|
TELEGRAM_OPS_EVENTS_PATH,
|
|
277
297
|
TELEGRAM_OPS_OVERVIEW_PATH,
|
|
278
298
|
TELEGRAM_OPS_REMINDERS_DUE_PATH,
|
|
@@ -282,6 +302,7 @@ export {
|
|
|
282
302
|
TELEGRAM_OPS_WEBHOOK_PATH,
|
|
283
303
|
TELEGRAM_WEBHOOK_PATH,
|
|
284
304
|
acceptsComments,
|
|
305
|
+
commentAnchorToken,
|
|
285
306
|
commentPolicyFromTags,
|
|
286
307
|
parsePostLocaleTag,
|
|
287
308
|
telegramOpsEventActionPath,
|
package/dist/messages.d.ts
CHANGED
|
@@ -36,12 +36,13 @@ export interface OwnerMessageCreateInput {
|
|
|
36
36
|
body: string;
|
|
37
37
|
displayName: string;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* receive the
|
|
39
|
+
* Required. The comment box channels rather than blocks, because a comment
|
|
40
|
+
* that nobody can answer is still worth publishing; a private message that
|
|
41
|
+
* nobody can answer is a dead letter. Supplying one triggers the shared
|
|
42
|
+
* lazy-verification mail, and only a verified address can ever receive the
|
|
43
|
+
* owner's reply — so this is necessary for a reply, not sufficient.
|
|
43
44
|
*/
|
|
44
|
-
email
|
|
45
|
+
email: string;
|
|
45
46
|
turnstileToken: string;
|
|
46
47
|
/** Minted by the form on load; proves the submit was not instant. */
|
|
47
48
|
dwellToken: string;
|
package/dist/mood.d.ts
CHANGED
|
@@ -188,13 +188,38 @@ export interface MoodLiveCount {
|
|
|
188
188
|
export interface MoodLiveCountsResponse {
|
|
189
189
|
counts: Record<string, MoodLiveCount>;
|
|
190
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* The parent a comment replies to, read from the t.me reply block.
|
|
193
|
+
* `id` is the parent comment id; `text` is a plain-text preview, not HTML.
|
|
194
|
+
*/
|
|
195
|
+
export interface MoodCommentReplyTo {
|
|
196
|
+
id: string;
|
|
197
|
+
author: string;
|
|
198
|
+
text: string;
|
|
199
|
+
}
|
|
200
|
+
/** Where a thread item came from. `telegram`: a group member wrote it in
|
|
201
|
+
the discussion thread. `web`: written on /mood/[id]; the group holds the
|
|
202
|
+
ops bot's bridged copy, and the read path re-attributes that copy to the
|
|
203
|
+
reader — plans/mood-comments-bridge.md "Read path: scrape plus overlay". */
|
|
204
|
+
export declare const MOOD_COMMENT_ORIGINS: readonly ['telegram', 'web'];
|
|
205
|
+
export type MoodCommentOrigin = (typeof MOOD_COMMENT_ORIGINS)[number];
|
|
191
206
|
export interface MoodComment {
|
|
207
|
+
/** Discussion-group message id as the t.me embed reports it. */
|
|
192
208
|
id: string;
|
|
193
209
|
author: string;
|
|
194
210
|
authorAvatar?: string;
|
|
195
211
|
datetime: string;
|
|
196
212
|
content: string;
|
|
197
213
|
reactions: MoodReaction[];
|
|
214
|
+
replyTo?: MoodCommentReplyTo;
|
|
215
|
+
/** Omitted means `telegram`. */
|
|
216
|
+
origin?: MoodCommentOrigin;
|
|
217
|
+
/** The site comment row behind a `web` item (`Comment.id`). Lets the
|
|
218
|
+
browser mark rows it wrote and offer edit/delete through /v2/comments. */
|
|
219
|
+
commentId?: string;
|
|
220
|
+
/** `commentAnchorToken(commentId)`; the row renders as `id="c-<token>"`
|
|
221
|
+
so the bridged Telegram link and the owner card land on it. */
|
|
222
|
+
anchorToken?: string;
|
|
198
223
|
}
|
|
199
224
|
export interface MoodCommentsPage {
|
|
200
225
|
comments: MoodComment[];
|
package/dist/mood.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/mood.ts
|
|
2
2
|
var MOOD_SENTIMENT_LABELS = ["joy", "calm", "melancholy", "anger", "anxiety", "neutral"];
|
|
3
3
|
var MOOD_AI_MODELS = ["gpt-5.5", "gpt-5", "claude-sonnet-4.6"];
|
|
4
|
+
var MOOD_COMMENT_ORIGINS = ["telegram", "web"];
|
|
4
5
|
export {
|
|
5
6
|
MOOD_AI_MODELS,
|
|
7
|
+
MOOD_COMMENT_ORIGINS,
|
|
6
8
|
MOOD_SENTIMENT_LABELS
|
|
7
9
|
};
|
package/dist/telegram-ops.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { AdminSubscriberPatch, BroadcastInput, BroadcastPreviewResult, BroadcastRecord, BroadcastSendResult } from './admin';
|
|
2
2
|
import type { DeliveryMode, NotifyChannel, SubscriberRecord, SubscriberStatus } from './notify';
|
|
3
3
|
export declare const TELEGRAM_OPS_WEBHOOK_PATH: '/webhooks/telegram-ops';
|
|
4
|
+
/**
|
|
5
|
+
* Callback data and message tokens the ops bot uses for comment cards. The
|
|
6
|
+
* comment id no longer lives in an expiring pending-action token: every
|
|
7
|
+
* card and every reply prompt carries `#c-<anchorToken>` in its View URL
|
|
8
|
+
* and as a text link, so replying to the card resolves the comment for as
|
|
9
|
+
* long as the card exists — plans/mood-comments-bridge.md "Owner reply
|
|
10
|
+
* flow". Group messages (the discussion group's automatic forwards and
|
|
11
|
+
* replies to the bot's bridged comments) are expected on this webhook too.
|
|
12
|
+
*/
|
|
13
|
+
export declare const TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES: {
|
|
14
|
+
readonly reply: 'comment:reply:';
|
|
15
|
+
readonly approve: 'comment:approve:';
|
|
16
|
+
readonly hide: 'comment:hide:';
|
|
17
|
+
readonly delete: 'comment:delete:';
|
|
18
|
+
};
|
|
4
19
|
export declare const NOTIFY_GATE_PATH: '/admin/notify-gate';
|
|
5
20
|
export declare const NOTIFY_GATE_RELEASE_PATH: "/admin/notify-gate/release";
|
|
6
21
|
export declare const NOTIFY_GATE_STATES: readonly ['open', 'held'];
|
package/dist/telegram-ops.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// src/telegram-ops.ts
|
|
2
2
|
var TELEGRAM_OPS_WEBHOOK_PATH = "/webhooks/telegram-ops";
|
|
3
|
+
var TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES = {
|
|
4
|
+
reply: "comment:reply:",
|
|
5
|
+
approve: "comment:approve:",
|
|
6
|
+
hide: "comment:hide:",
|
|
7
|
+
delete: "comment:delete:"
|
|
8
|
+
};
|
|
3
9
|
var NOTIFY_GATE_PATH = "/admin/notify-gate";
|
|
4
10
|
var NOTIFY_GATE_RELEASE_PATH = `${NOTIFY_GATE_PATH}/release`;
|
|
5
11
|
var NOTIFY_GATE_STATES = ["open", "held"];
|
|
@@ -61,6 +67,7 @@ export {
|
|
|
61
67
|
TELEGRAM_OPS_BROADCAST_PREVIEW_PATH,
|
|
62
68
|
TELEGRAM_OPS_BROADCAST_SEND_PATH,
|
|
63
69
|
TELEGRAM_OPS_BROADCAST_STATUSES,
|
|
70
|
+
TELEGRAM_OPS_COMMENT_CALLBACK_PREFIXES,
|
|
64
71
|
TELEGRAM_OPS_EVENTS_PATH,
|
|
65
72
|
TELEGRAM_OPS_OVERVIEW_PATH,
|
|
66
73
|
TELEGRAM_OPS_REMINDERS_DUE_PATH,
|