@notidotbot/noti-api-client 1.9.86 → 1.9.88

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.
@@ -0,0 +1,552 @@
1
+ import { CancelOutWebResponses } from '../types';
2
+ import { WebDataManager } from '../core/manager';
3
+ /**
4
+ * The feedback board - ideas and private requests, votes, follows (replaces Fider at feedback.noti.bot).
5
+ *
6
+ * ── WHO SEES WHAT ──────────────────────────────────────────────────────────
7
+ * Reads work logged out: pass `auth` ONLY when the user is logged in. With a session the server answers for
8
+ * that user (premium-only boards, their own hidden posts, staff view) and marks the response `private,
9
+ * no-store`; without one it answers the public view and lets it be cached. Never cache a logged-in response
10
+ * on the page side as if it were public.
11
+ *
12
+ * Behind the `feedback` launch flag: while it is hidden for the caller every method 404s. A post the viewer
13
+ * may not see is 404 too, never 403 - do not show "forbidden", show "not found".
14
+ *
15
+ * ── REFUSALS ───────────────────────────────────────────────────────────────
16
+ * A refused write is `{ status, error: { code } }`, `code` from `FeedbackErrorCode`. `voteRefusal` /
17
+ * `commentRefusal` on a post's `viewer` tell you up front why the buttons are off (banned, new account,
18
+ * archived board, locked post), so the page can say so instead of letting the click fail.
19
+ *
20
+ * ── TEXT ───────────────────────────────────────────────────────────────────
21
+ * `title` is plain text. `body` is MARKDOWN, stored raw: render it with an allowlist (no raw HTML, images
22
+ * only from Noti's own host, links `rel="nofollow ugc noopener"`). The server does not sanitise HTML for you.
23
+ */
24
+ export declare class APIFeedback {
25
+ private web;
26
+ constructor(web: WebDataManager);
27
+ /** Boards the viewer may see, with visible-post counts per status. */
28
+ getFeedbackBoards({ auth }?: FeedbackFunctionsInput['getFeedbackBoards']): Promise<import("../types").WebResponse<{
29
+ boards: FeedbackBoard[];
30
+ }>>;
31
+ getFeedbackTags({ auth }?: FeedbackFunctionsInput['getFeedbackTags']): Promise<import("../types").WebResponse<{
32
+ tags: FeedbackTag[];
33
+ }>>;
34
+ /**
35
+ * The list. `board` / `tag` take a slug or id; `sort` is `top` (default, pinned first inside a board),
36
+ * `new` or `trending` (votes in the last 14 days). `limit` up to 50. Searching logged out is rate limited.
37
+ */
38
+ getFeedbackPosts({ auth, ...query }?: FeedbackFunctionsInput['getFeedbackPosts']): Promise<import("../types").WebResponse<FeedbackPostList>>;
39
+ /** Up to 5 visible posts whose titles look like `q` - the "is this already asked?" hint while typing a title. */
40
+ getSimilarFeedbackPosts({ auth, q }: FeedbackFunctionsInput['getSimilarFeedbackPosts']): Promise<import("../types").WebResponse<{
41
+ posts: FeedbackSimilarPost[];
42
+ }>>;
43
+ /**
44
+ * One post. A merged post answers `{ post: null, viewer: null, mergedInto }`: send the user to
45
+ * `mergedInto` (its old number keeps working this way).
46
+ */
47
+ getFeedbackPost({ auth, number }: FeedbackFunctionsInput['getFeedbackPost']): Promise<import("../types").WebResponse<FeedbackPostView>>;
48
+ /** Post an idea / request. The author votes and follows it automatically. 5 per hour. `uploadIds` from createFeedbackUpload. */
49
+ createFeedbackPost({ auth, post }: FeedbackFunctionsInput['createFeedbackPost']): Promise<import("../types").WebResponse<FeedbackPostView>>;
50
+ /** Edit your own post. Only while it is open / under review and has 10 or fewer votes (`viewer.canEdit`). */
51
+ updateFeedbackPost({ auth, number, post }: FeedbackFunctionsInput['updateFeedbackPost']): Promise<import("../types").WebResponse<FeedbackPostView>>;
52
+ /** Delete your own post - only while nobody else has voted on it (`ask_staff` otherwise). */
53
+ deleteFeedbackPost({ auth, number }: FeedbackFunctionsInput['deleteFeedbackPost']): Promise<import("../types").WebResponse<{
54
+ deleted: true;
55
+ }>>;
56
+ /** Vote. Voting also follows the post, unless the user unfollowed it before. 30 per minute. */
57
+ createFeedbackVote({ auth, number }: FeedbackFunctionsInput['createFeedbackVote']): Promise<import("../types").WebResponse<FeedbackPostView>>;
58
+ deleteFeedbackVote({ auth, number }: FeedbackFunctionsInput['deleteFeedbackVote']): Promise<import("../types").WebResponse<FeedbackPostView>>;
59
+ /** Follow: get notified when it changes status, ships, or gets an official answer. */
60
+ createFeedbackFollow({ auth, number }: FeedbackFunctionsInput['createFeedbackFollow']): Promise<import("../types").WebResponse<FeedbackPostView>>;
61
+ /** Unfollow. Sticks: voting again later does not re-follow. */
62
+ deleteFeedbackFollow({ auth, number }: FeedbackFunctionsInput['deleteFeedbackFollow']): Promise<import("../types").WebResponse<FeedbackPostView>>;
63
+ /**
64
+ * The thread: the official staff answer first, then comments oldest first with their replies (one level).
65
+ * Staff notes (`internal`) only reach staff. A deleted comment shows as `deleted: true` with no body while it
66
+ * still has replies.
67
+ */
68
+ getFeedbackComments({ auth, number }: FeedbackFunctionsInput['getFeedbackComments']): Promise<import("../types").WebResponse<FeedbackThread>>;
69
+ /** Comment, or reply with `parentId` (a top-level comment only). `uploadIds` from createFeedbackUpload. 10/minute. Returns the thread. */
70
+ createFeedbackComment({ auth, number, comment }: FeedbackFunctionsInput['createFeedbackComment']): Promise<import("../types").WebResponse<FeedbackThread>>;
71
+ /** Edit your own comment. Returns the thread. */
72
+ updateFeedbackComment({ auth, commentId, body }: FeedbackFunctionsInput['updateFeedbackComment']): Promise<import("../types").WebResponse<FeedbackThread>>;
73
+ /** Delete your own comment (its replies stay). Returns the thread. */
74
+ deleteFeedbackComment({ auth, commentId }: FeedbackFunctionsInput['deleteFeedbackComment']): Promise<import("../types").WebResponse<FeedbackThread>>;
75
+ /** React to a post. Answers the post's reactions. `reactions_off` (404) when the switch is off. */
76
+ createFeedbackPostReaction({ auth, number, kind }: FeedbackFunctionsInput['createFeedbackPostReaction']): Promise<import("../types").WebResponse<{
77
+ reactions: FeedbackReactions;
78
+ }>>;
79
+ deleteFeedbackPostReaction({ auth, number, kind }: FeedbackFunctionsInput['deleteFeedbackPostReaction']): Promise<import("../types").WebResponse<{
80
+ reactions: FeedbackReactions;
81
+ }>>;
82
+ createFeedbackCommentReaction({ auth, commentId, kind }: FeedbackFunctionsInput['createFeedbackCommentReaction']): Promise<import("../types").WebResponse<{
83
+ reactions: FeedbackReactions;
84
+ }>>;
85
+ deleteFeedbackCommentReaction({ auth, commentId, kind }: FeedbackFunctionsInput['deleteFeedbackCommentReaction']): Promise<import("../types").WebResponse<{
86
+ reactions: FeedbackReactions;
87
+ }>>;
88
+ /**
89
+ * Upload one image (field `image`: png, jpeg, webp or gif, up to 8 MB). It comes back as webp, at most
90
+ * 2000px, with its metadata (GPS, camera) removed. Put `url` in the markdown as `![alt](url)` and send
91
+ * `uploadId` in `uploadIds` when saving the post or comment; unattached uploads are deleted after 24h.
92
+ * 20 per day. Refusals: `uploads_off`, `image_too_large`, `unsupported_image`, `invalid_image`, `upload_quota`.
93
+ */
94
+ createFeedbackUpload({ auth, formData }: FeedbackFunctionsInput['createFeedbackUpload']): Promise<import("../types").WebResponse<FeedbackUpload>>;
95
+ /** The roadmap: planned, in progress, and shipped in the last 30 days (older ones are on the timeline). */
96
+ getFeedbackRoadmap({ auth }?: FeedbackFunctionsInput['getFeedbackRoadmap']): Promise<import("../types").WebResponse<FeedbackRoadmap>>;
97
+ /** Shipped posts, newest first, each with the public changelog entry it shipped in (if staff linked one). */
98
+ getFeedbackTimeline({ auth, ...query }?: FeedbackFunctionsInput['getFeedbackTimeline']): Promise<import("../types").WebResponse<FeedbackTimeline>>;
99
+ /** Posts staff featured for the top of the feedback page (up to 10, in their order). */
100
+ getFeedbackFeatured({ auth }?: FeedbackFunctionsInput['getFeedbackFeatured']): Promise<import("../types").WebResponse<{
101
+ posts: FeedbackPostList["posts"];
102
+ }>>;
103
+ /**
104
+ * The inbox (the bell): newest first. Visibility is checked when you read it, so a post that has gone
105
+ * private is simply not there. Page with `before` = the previous page's `next`.
106
+ */
107
+ getFeedbackNotifications({ auth, ...query }: FeedbackFunctionsInput['getFeedbackNotifications']): Promise<import("../types").WebResponse<FeedbackNotificationList>>;
108
+ /** The bell's number. Counts up to 100 (`capped` then true - show "99+"). */
109
+ getFeedbackNotificationCount({ auth }: FeedbackFunctionsInput['getFeedbackNotificationCount']): Promise<import("../types").WebResponse<{
110
+ unread: number;
111
+ capped: boolean;
112
+ }>>;
113
+ /** Mark notifications read: the given `ids`, or all of them when omitted. */
114
+ createFeedbackNotificationReads({ auth, ids }: FeedbackFunctionsInput['createFeedbackNotificationReads']): Promise<import("../types").WebResponse<{
115
+ read: number;
116
+ }>>;
117
+ /** DM settings. `dmBlocked`: Discord refused a DM (closed DMs) - switching DMs on again clears it. `dmsAvailable` false = DMs are off for everyone. */
118
+ getFeedbackSettings({ auth }: FeedbackFunctionsInput['getFeedbackSettings']): Promise<import("../types").WebResponse<FeedbackSettings>>;
119
+ /** Turn the update digest DM on or off (off by default; at most one DM per 15 minutes, 10 a day). */
120
+ updateFeedbackSettings({ auth, dmEnabled }: FeedbackFunctionsInput['updateFeedbackSettings']): Promise<import("../types").WebResponse<FeedbackSettings>>;
121
+ /** The "my feedback" page: my posts, what I voted on, what I follow (100 each, newest first). */
122
+ getMyFeedback({ auth }: FeedbackFunctionsInput['getMyFeedback']): Promise<import("../types").WebResponse<{
123
+ posts: FeedbackPost[];
124
+ voted: FeedbackPost[];
125
+ following: FeedbackPost[];
126
+ }>>;
127
+ }
128
+ export type FeedbackFunctionsInput = {
129
+ 'getFeedbackBoards': {
130
+ auth?: string;
131
+ };
132
+ 'getFeedbackTags': {
133
+ auth?: string;
134
+ };
135
+ 'getFeedbackPosts': {
136
+ auth?: string;
137
+ board?: string;
138
+ tag?: string;
139
+ status?: FeedbackStatus;
140
+ q?: string;
141
+ sort?: FeedbackSort;
142
+ offset?: number;
143
+ limit?: number;
144
+ };
145
+ 'getSimilarFeedbackPosts': {
146
+ auth?: string;
147
+ q: string;
148
+ };
149
+ 'getFeedbackPost': {
150
+ auth?: string;
151
+ number: number;
152
+ };
153
+ 'createFeedbackPost': {
154
+ auth: string;
155
+ post: {
156
+ boardId: string;
157
+ title: string;
158
+ body?: string;
159
+ tagIds?: string[];
160
+ uploadIds?: string[];
161
+ };
162
+ };
163
+ 'updateFeedbackPost': {
164
+ auth: string;
165
+ number: number;
166
+ post: {
167
+ title?: string;
168
+ body?: string;
169
+ tagIds?: string[];
170
+ uploadIds?: string[];
171
+ };
172
+ };
173
+ 'deleteFeedbackPost': {
174
+ auth: string;
175
+ number: number;
176
+ };
177
+ 'createFeedbackVote': {
178
+ auth: string;
179
+ number: number;
180
+ };
181
+ 'deleteFeedbackVote': {
182
+ auth: string;
183
+ number: number;
184
+ };
185
+ 'createFeedbackFollow': {
186
+ auth: string;
187
+ number: number;
188
+ };
189
+ 'deleteFeedbackFollow': {
190
+ auth: string;
191
+ number: number;
192
+ };
193
+ 'getMyFeedback': {
194
+ auth: string;
195
+ };
196
+ 'getFeedbackNotifications': {
197
+ auth: string;
198
+ before?: string;
199
+ limit?: number;
200
+ };
201
+ 'getFeedbackNotificationCount': {
202
+ auth: string;
203
+ };
204
+ 'createFeedbackNotificationReads': {
205
+ auth: string;
206
+ ids?: string[];
207
+ };
208
+ 'getFeedbackSettings': {
209
+ auth: string;
210
+ };
211
+ 'updateFeedbackSettings': {
212
+ auth: string;
213
+ dmEnabled: boolean;
214
+ };
215
+ 'getFeedbackRoadmap': {
216
+ auth?: string;
217
+ };
218
+ 'getFeedbackTimeline': {
219
+ auth?: string;
220
+ offset?: number;
221
+ limit?: number;
222
+ };
223
+ 'getFeedbackFeatured': {
224
+ auth?: string;
225
+ };
226
+ 'getFeedbackComments': {
227
+ auth?: string;
228
+ number: number;
229
+ };
230
+ 'createFeedbackComment': {
231
+ auth: string;
232
+ number: number;
233
+ comment: {
234
+ body: string;
235
+ parentId?: string;
236
+ uploadIds?: string[];
237
+ };
238
+ };
239
+ 'updateFeedbackComment': {
240
+ auth: string;
241
+ commentId: string;
242
+ body: string;
243
+ };
244
+ 'deleteFeedbackComment': {
245
+ auth: string;
246
+ commentId: string;
247
+ };
248
+ 'createFeedbackPostReaction': {
249
+ auth: string;
250
+ number: number;
251
+ kind: FeedbackReactionKind;
252
+ };
253
+ 'deleteFeedbackPostReaction': {
254
+ auth: string;
255
+ number: number;
256
+ kind: FeedbackReactionKind;
257
+ };
258
+ 'createFeedbackCommentReaction': {
259
+ auth: string;
260
+ commentId: string;
261
+ kind: FeedbackReactionKind;
262
+ };
263
+ 'deleteFeedbackCommentReaction': {
264
+ auth: string;
265
+ commentId: string;
266
+ kind: FeedbackReactionKind;
267
+ };
268
+ /** FormData with one file under `image`. */
269
+ 'createFeedbackUpload': {
270
+ auth: string;
271
+ formData: FormData;
272
+ };
273
+ };
274
+ export type FeedbackGetReturnTypes = {
275
+ 'getFeedbackBoardsRaw': Awaited<ReturnType<APIFeedback['getFeedbackBoards']>>;
276
+ 'getFeedbackBoardsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackBoards']>>>;
277
+ 'getFeedbackTagsRaw': Awaited<ReturnType<APIFeedback['getFeedbackTags']>>;
278
+ 'getFeedbackTagsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackTags']>>>;
279
+ 'getFeedbackPostsRaw': Awaited<ReturnType<APIFeedback['getFeedbackPosts']>>;
280
+ 'getFeedbackPostsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackPosts']>>>;
281
+ 'getSimilarFeedbackPostsRaw': Awaited<ReturnType<APIFeedback['getSimilarFeedbackPosts']>>;
282
+ 'getSimilarFeedbackPostsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getSimilarFeedbackPosts']>>>;
283
+ 'getFeedbackPostRaw': Awaited<ReturnType<APIFeedback['getFeedbackPost']>>;
284
+ 'getFeedbackPostSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackPost']>>>;
285
+ 'createFeedbackPostRaw': Awaited<ReturnType<APIFeedback['createFeedbackPost']>>;
286
+ 'createFeedbackPostSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackPost']>>>;
287
+ 'updateFeedbackPostRaw': Awaited<ReturnType<APIFeedback['updateFeedbackPost']>>;
288
+ 'updateFeedbackPostSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['updateFeedbackPost']>>>;
289
+ 'deleteFeedbackPostRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackPost']>>;
290
+ 'deleteFeedbackPostSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackPost']>>>;
291
+ 'createFeedbackVoteRaw': Awaited<ReturnType<APIFeedback['createFeedbackVote']>>;
292
+ 'createFeedbackVoteSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackVote']>>>;
293
+ 'deleteFeedbackVoteRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackVote']>>;
294
+ 'deleteFeedbackVoteSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackVote']>>>;
295
+ 'createFeedbackFollowRaw': Awaited<ReturnType<APIFeedback['createFeedbackFollow']>>;
296
+ 'createFeedbackFollowSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackFollow']>>>;
297
+ 'deleteFeedbackFollowRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackFollow']>>;
298
+ 'deleteFeedbackFollowSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackFollow']>>>;
299
+ 'getMyFeedbackRaw': Awaited<ReturnType<APIFeedback['getMyFeedback']>>;
300
+ 'getMyFeedbackSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getMyFeedback']>>>;
301
+ 'getFeedbackNotificationsRaw': Awaited<ReturnType<APIFeedback['getFeedbackNotifications']>>;
302
+ 'getFeedbackNotificationsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackNotifications']>>>;
303
+ 'getFeedbackNotificationCountRaw': Awaited<ReturnType<APIFeedback['getFeedbackNotificationCount']>>;
304
+ 'getFeedbackNotificationCountSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackNotificationCount']>>>;
305
+ 'createFeedbackNotificationReadsRaw': Awaited<ReturnType<APIFeedback['createFeedbackNotificationReads']>>;
306
+ 'createFeedbackNotificationReadsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackNotificationReads']>>>;
307
+ 'getFeedbackSettingsRaw': Awaited<ReturnType<APIFeedback['getFeedbackSettings']>>;
308
+ 'getFeedbackSettingsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackSettings']>>>;
309
+ 'updateFeedbackSettingsRaw': Awaited<ReturnType<APIFeedback['updateFeedbackSettings']>>;
310
+ 'updateFeedbackSettingsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['updateFeedbackSettings']>>>;
311
+ 'getFeedbackRoadmapRaw': Awaited<ReturnType<APIFeedback['getFeedbackRoadmap']>>;
312
+ 'getFeedbackRoadmapSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackRoadmap']>>>;
313
+ 'getFeedbackTimelineRaw': Awaited<ReturnType<APIFeedback['getFeedbackTimeline']>>;
314
+ 'getFeedbackTimelineSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackTimeline']>>>;
315
+ 'getFeedbackFeaturedRaw': Awaited<ReturnType<APIFeedback['getFeedbackFeatured']>>;
316
+ 'getFeedbackFeaturedSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackFeatured']>>>;
317
+ 'getFeedbackCommentsRaw': Awaited<ReturnType<APIFeedback['getFeedbackComments']>>;
318
+ 'getFeedbackCommentsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['getFeedbackComments']>>>;
319
+ 'createFeedbackCommentRaw': Awaited<ReturnType<APIFeedback['createFeedbackComment']>>;
320
+ 'createFeedbackCommentSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackComment']>>>;
321
+ 'updateFeedbackCommentRaw': Awaited<ReturnType<APIFeedback['updateFeedbackComment']>>;
322
+ 'updateFeedbackCommentSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['updateFeedbackComment']>>>;
323
+ 'deleteFeedbackCommentRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackComment']>>;
324
+ 'deleteFeedbackCommentSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackComment']>>>;
325
+ 'createFeedbackPostReactionRaw': Awaited<ReturnType<APIFeedback['createFeedbackPostReaction']>>;
326
+ 'createFeedbackPostReactionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackPostReaction']>>>;
327
+ 'deleteFeedbackPostReactionRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackPostReaction']>>;
328
+ 'deleteFeedbackPostReactionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackPostReaction']>>>;
329
+ 'createFeedbackCommentReactionRaw': Awaited<ReturnType<APIFeedback['createFeedbackCommentReaction']>>;
330
+ 'createFeedbackCommentReactionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackCommentReaction']>>>;
331
+ 'deleteFeedbackCommentReactionRaw': Awaited<ReturnType<APIFeedback['deleteFeedbackCommentReaction']>>;
332
+ 'deleteFeedbackCommentReactionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['deleteFeedbackCommentReaction']>>>;
333
+ 'createFeedbackUploadRaw': Awaited<ReturnType<APIFeedback['createFeedbackUpload']>>;
334
+ 'createFeedbackUploadSuccess': CancelOutWebResponses<Awaited<ReturnType<APIFeedback['createFeedbackUpload']>>>;
335
+ };
336
+ /** Mirrors `FeedbackAudienceEnum` in Noti-v3. A post's audience is the narrower of its board's and its own. */
337
+ export declare const FEEDBACK_AUDIENCES: readonly ["public", "premium", "beta", "staff", "authorAndStaff"];
338
+ export type FeedbackAudience = typeof FEEDBACK_AUDIENCES[number];
339
+ /** Mirrors `FeedbackStatusEnum`. The roadmap columns are planned / inProgress / done. */
340
+ export declare const FEEDBACK_STATUSES: readonly ["open", "underReview", "planned", "inProgress", "done", "declined"];
341
+ export type FeedbackStatus = typeof FEEDBACK_STATUSES[number];
342
+ export type FeedbackSort = 'top' | 'new' | 'trending';
343
+ /** Why an action was refused. Mirrors `FEEDBACK_REFUSALS` in Noti-v3's `src/modules/feedback/visibility.ts`. */
344
+ export declare const FEEDBACK_REFUSALS: readonly ["login_required", "not_visible", "banned", "account_too_new", "board_archived", "posting_closed", "voting_locked", "comments_locked"];
345
+ export type FeedbackRefusal = typeof FEEDBACK_REFUSALS[number];
346
+ /** Every `error.code` the member routes return. */
347
+ export type FeedbackErrorCode = FeedbackRefusal | 'rate_limited' | 'busy' | 'title_too_short' | 'invalid_tag' | 'not_editable' | 'not_author' | 'ask_staff' | 'html_not_allowed' | 'foreign_image' | 'invalid_upload' | 'parent_not_found' | 'reply_depth' | 'invalid_reaction' | 'reactions_off' | 'uploads_off' | 'image_too_large' | 'unsupported_image' | 'invalid_image' | 'upload_quota' | 'no_image' | 'upload_failed';
348
+ export type FeedbackBoard = {
349
+ boardId: string;
350
+ slug: string;
351
+ name: string;
352
+ description: string | null;
353
+ audience: FeedbackAudience;
354
+ archived: boolean;
355
+ /** False when archived or closed to new posts (votes and comments may still be open). */
356
+ postingOpen: boolean;
357
+ position: number;
358
+ /** Visible posts per status, e.g. `{ open: 12, planned: 3 }`. */
359
+ counts: Partial<Record<FeedbackStatus, number>>;
360
+ };
361
+ export type FeedbackTag = {
362
+ tagId: string;
363
+ slug: string;
364
+ name: string; /** Hex, no #. */
365
+ color: string;
366
+ };
367
+ /** One post as the site shows it. Mirrors `FeedbackPostCard` in Noti-v3. */
368
+ export type FeedbackPost = {
369
+ number: number;
370
+ board: {
371
+ boardId: string;
372
+ slug: string;
373
+ name: string;
374
+ } | null;
375
+ title: string;
376
+ /** Markdown - render with an allowlist. */
377
+ body: string;
378
+ status: FeedbackStatus;
379
+ /** Effective audience - show a badge ("Premium only") when not `public`. */
380
+ audience: FeedbackAudience;
381
+ /** Only on declined posts. */
382
+ declinedReason: string | null;
383
+ /** "2026-11" or "2026-Q4", only on planned / inProgress posts. */
384
+ eta: string | null;
385
+ tags: FeedbackTag[];
386
+ /** Includes `importedVotes`. */
387
+ voteCount: number;
388
+ /** Votes carried over from the old Fider board. */
389
+ importedVotes: number;
390
+ author: {
391
+ name: string;
392
+ isYou: boolean;
393
+ isStaff: boolean;
394
+ };
395
+ pinned: boolean;
396
+ featured: boolean;
397
+ /** True only for the author or staff - the post is hidden from everyone else. */
398
+ hidden: boolean;
399
+ /** Shown to the author when staff hid it. */
400
+ hiddenReason: string | null;
401
+ votingLocked: boolean;
402
+ commentsLocked: boolean;
403
+ mergedInto: number | null;
404
+ /** ISO. */
405
+ shippedAt: string | null;
406
+ editedAt: string | null;
407
+ createdAt: string;
408
+ };
409
+ export type FeedbackPostViewer = {
410
+ voted: boolean;
411
+ following: boolean;
412
+ canVote: boolean;
413
+ voteRefusal: FeedbackRefusal | null;
414
+ canComment: boolean;
415
+ commentRefusal: FeedbackRefusal | null;
416
+ canEdit: boolean;
417
+ };
418
+ export type FeedbackPostView = {
419
+ post: FeedbackPost & {
420
+ commentCount: number;
421
+ changelog: FeedbackChangelogLink | null;
422
+ polls: FeedbackPostPoll[];
423
+ };
424
+ viewer: FeedbackPostViewer;
425
+ reactions: FeedbackReactions;
426
+ mergedInto: null;
427
+ } | {
428
+ post: null;
429
+ viewer: null;
430
+ reactions: null;
431
+ mergedInto: number;
432
+ };
433
+ export type FeedbackPostList = {
434
+ /** `commentCount` counts visible comments only (no staff notes, hidden or deleted). */
435
+ posts: Array<{
436
+ post: FeedbackPost & {
437
+ commentCount: number;
438
+ };
439
+ viewer: {
440
+ voted: boolean;
441
+ following: boolean;
442
+ };
443
+ }>;
444
+ total: number;
445
+ offset: number;
446
+ limit: number;
447
+ };
448
+ export type FeedbackSimilarPost = {
449
+ number: number;
450
+ title: string;
451
+ status: FeedbackStatus;
452
+ voteCount: number;
453
+ };
454
+ /** Mirrors `FEEDBACK_REACTION_KINDS` in Noti-v3. */
455
+ export declare const FEEDBACK_REACTION_KINDS: readonly ["heart", "thumbsUp", "thumbsDown", "fire"];
456
+ export type FeedbackReactionKind = typeof FEEDBACK_REACTION_KINDS[number];
457
+ export type FeedbackReactions = {
458
+ counts: Record<FeedbackReactionKind, number>;
459
+ /** This viewer's own. */
460
+ mine: FeedbackReactionKind[];
461
+ };
462
+ export type FeedbackComment = {
463
+ commentId: string;
464
+ parentId: string | null;
465
+ /** Markdown; null when deleted. */
466
+ body: string | null;
467
+ deleted: boolean;
468
+ author: {
469
+ name: string;
470
+ isYou: boolean;
471
+ isStaff: boolean;
472
+ } | null;
473
+ /** A staff-only note - only staff ever receive these. */
474
+ internal: boolean;
475
+ official: boolean;
476
+ /** True only for the author or staff. */
477
+ hidden: boolean;
478
+ editedAt: string | null;
479
+ createdAt: string;
480
+ canEdit: boolean;
481
+ reactions: FeedbackReactions | null;
482
+ };
483
+ export type FeedbackThread = {
484
+ /** The pinned staff answer, shown first. */
485
+ official: FeedbackComment | null;
486
+ comments: Array<FeedbackComment & {
487
+ replies: FeedbackComment[];
488
+ }>;
489
+ total: number;
490
+ };
491
+ export type FeedbackUpload = {
492
+ uploadId: string;
493
+ url: string;
494
+ width: number;
495
+ height: number;
496
+ };
497
+ /** A public changelog entry a shipped post was linked to. `identifier` is the changelog's own id on the site. */
498
+ export type FeedbackChangelogLink = {
499
+ identifier: string;
500
+ title: string;
501
+ date: string;
502
+ };
503
+ /** An open site poll this post is an option in - show "in a poll now" and link to the poll. */
504
+ export type FeedbackPostPoll = {
505
+ pollId: string;
506
+ title: string;
507
+ closesAt: string | null;
508
+ optionId: string;
509
+ };
510
+ export type FeedbackRoadmap = {
511
+ planned: Array<FeedbackPost & {
512
+ commentCount: number;
513
+ }>;
514
+ inProgress: Array<FeedbackPost & {
515
+ commentCount: number;
516
+ }>;
517
+ /** Shipped in the last 30 days, newest first. */
518
+ done: Array<FeedbackPost & {
519
+ commentCount: number;
520
+ }>;
521
+ };
522
+ export type FeedbackTimeline = {
523
+ entries: Array<{
524
+ post: FeedbackPost;
525
+ changelog: FeedbackChangelogLink | null;
526
+ }>;
527
+ total: number;
528
+ offset: number;
529
+ limit: number;
530
+ };
531
+ /** Mirrors `FEEDBACK_NOTIFICATION_KINDS` in Noti-v3. `merged`: the post you followed was merged into `post`. */
532
+ export declare const FEEDBACK_NOTIFICATION_KINDS: readonly ["statusChanged", "shipped", "officialAnswer", "reply", "merged", "pollStarted"];
533
+ export type FeedbackNotificationKind = typeof FEEDBACK_NOTIFICATION_KINDS[number];
534
+ export type FeedbackNotification = {
535
+ notificationId: string;
536
+ kind: FeedbackNotificationKind;
537
+ /** The reply or official answer, for `reply` / `officialAnswer`. */
538
+ commentId: string | null;
539
+ read: boolean;
540
+ createdAt: string;
541
+ /** As it is NOW (title and status are live, not a copy from when it happened). */
542
+ post: FeedbackPost;
543
+ };
544
+ export type FeedbackNotificationList = {
545
+ notifications: FeedbackNotification[];
546
+ next: string | null;
547
+ };
548
+ export type FeedbackSettings = {
549
+ dmEnabled: boolean;
550
+ dmBlocked: boolean;
551
+ dmsAvailable: boolean;
552
+ };