@notidotbot/noti-api-client 1.9.85 → 1.9.87
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/classes/adminFeedback.d.ts +336 -0
- package/dist/classes/adminFeedback.js +159 -0
- package/dist/classes/adminPolls.d.ts +4 -0
- package/dist/classes/feedback.d.ts +550 -0
- package/dist/classes/feedback.js +274 -0
- package/dist/classes/guildEmojis.d.ts +120 -0
- package/dist/classes/guildEmojis.js +55 -0
- package/dist/classes/polls.d.ts +2 -0
- package/dist/classes/socials.d.ts +75 -0
- package/dist/classes/socials.js +45 -0
- package/dist/core/manager.d.ts +8 -0
- package/dist/core/manager.js +8 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/other/bitfields.d.ts +10 -4
- package/dist/other/bitfields.js +6 -0
- package/dist/other/features.d.ts +1 -1
- package/dist/other/features.js +5 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FEEDBACK_NOTIFICATION_KINDS = exports.FEEDBACK_REACTION_KINDS = exports.FEEDBACK_REFUSALS = exports.FEEDBACK_STATUSES = exports.FEEDBACK_AUDIENCES = exports.APIFeedback = void 0;
|
|
4
|
+
// Data.
|
|
5
|
+
/**
|
|
6
|
+
* The feedback board - ideas and private requests, votes, follows (replaces Fider at feedback.noti.bot).
|
|
7
|
+
*
|
|
8
|
+
* ── WHO SEES WHAT ──────────────────────────────────────────────────────────
|
|
9
|
+
* Reads work logged out: pass `auth` ONLY when the user is logged in. With a session the server answers for
|
|
10
|
+
* that user (premium-only boards, their own hidden posts, staff view) and marks the response `private,
|
|
11
|
+
* no-store`; without one it answers the public view and lets it be cached. Never cache a logged-in response
|
|
12
|
+
* on the page side as if it were public.
|
|
13
|
+
*
|
|
14
|
+
* Behind the `feedback` launch flag: while it is hidden for the caller every method 404s. A post the viewer
|
|
15
|
+
* may not see is 404 too, never 403 - do not show "forbidden", show "not found".
|
|
16
|
+
*
|
|
17
|
+
* ── REFUSALS ───────────────────────────────────────────────────────────────
|
|
18
|
+
* A refused write is `{ status, error: { code } }`, `code` from `FeedbackErrorCode`. `voteRefusal` /
|
|
19
|
+
* `commentRefusal` on a post's `viewer` tell you up front why the buttons are off (banned, new account,
|
|
20
|
+
* archived board, locked post), so the page can say so instead of letting the click fail.
|
|
21
|
+
*
|
|
22
|
+
* ── TEXT ───────────────────────────────────────────────────────────────────
|
|
23
|
+
* `title` is plain text. `body` is MARKDOWN, stored raw: render it with an allowlist (no raw HTML, images
|
|
24
|
+
* only from Noti's own host, links `rel="nofollow ugc noopener"`). The server does not sanitise HTML for you.
|
|
25
|
+
*/
|
|
26
|
+
class APIFeedback {
|
|
27
|
+
web;
|
|
28
|
+
constructor(web) {
|
|
29
|
+
this.web = web;
|
|
30
|
+
}
|
|
31
|
+
/** Boards the viewer may see, with visible-post counts per status. */
|
|
32
|
+
async getFeedbackBoards({ auth } = {}) {
|
|
33
|
+
return await this.web.request({
|
|
34
|
+
method: 'GET', auth,
|
|
35
|
+
endpoint: this.web.qp('/public/feedback/boards'),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async getFeedbackTags({ auth } = {}) {
|
|
39
|
+
return await this.web.request({
|
|
40
|
+
method: 'GET', auth,
|
|
41
|
+
endpoint: this.web.qp('/public/feedback/tags'),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The list. `board` / `tag` take a slug or id; `sort` is `top` (default, pinned first inside a board),
|
|
46
|
+
* `new` or `trending` (votes in the last 14 days). `limit` up to 50. Searching logged out is rate limited.
|
|
47
|
+
*/
|
|
48
|
+
async getFeedbackPosts({ auth, ...query } = {}) {
|
|
49
|
+
return await this.web.request({
|
|
50
|
+
method: 'GET', auth,
|
|
51
|
+
endpoint: this.web.qp('/public/feedback/posts', query),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Up to 5 visible posts whose titles look like `q` - the "is this already asked?" hint while typing a title. */
|
|
55
|
+
async getSimilarFeedbackPosts({ auth, q }) {
|
|
56
|
+
return await this.web.request({
|
|
57
|
+
method: 'GET', auth,
|
|
58
|
+
endpoint: this.web.qp('/public/feedback/posts/similar', { q }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One post. A merged post answers `{ post: null, viewer: null, mergedInto }`: send the user to
|
|
63
|
+
* `mergedInto` (its old number keeps working this way).
|
|
64
|
+
*/
|
|
65
|
+
async getFeedbackPost({ auth, number }) {
|
|
66
|
+
return await this.web.request({
|
|
67
|
+
method: 'GET', auth,
|
|
68
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Post an idea / request. The author votes and follows it automatically. 5 per hour. */
|
|
72
|
+
async createFeedbackPost({ auth, post }) {
|
|
73
|
+
return await this.web.request({
|
|
74
|
+
method: 'POST', auth,
|
|
75
|
+
endpoint: this.web.qp('/public/feedback/posts'),
|
|
76
|
+
body: post,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** Edit your own post. Only while it is open / under review and has 10 or fewer votes (`viewer.canEdit`). */
|
|
80
|
+
async updateFeedbackPost({ auth, number, post }) {
|
|
81
|
+
return await this.web.request({
|
|
82
|
+
method: 'PATCH', auth,
|
|
83
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number),
|
|
84
|
+
body: post,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Delete your own post - only while nobody else has voted on it (`ask_staff` otherwise). */
|
|
88
|
+
async deleteFeedbackPost({ auth, number }) {
|
|
89
|
+
return await this.web.request({
|
|
90
|
+
method: 'DELETE', auth,
|
|
91
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Vote. Voting also follows the post, unless the user unfollowed it before. 30 per minute. */
|
|
95
|
+
async createFeedbackVote({ auth, number }) {
|
|
96
|
+
return await this.web.request({
|
|
97
|
+
method: 'PUT', auth,
|
|
98
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/votes/@me'),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async deleteFeedbackVote({ auth, number }) {
|
|
102
|
+
return await this.web.request({
|
|
103
|
+
method: 'DELETE', auth,
|
|
104
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/votes/@me'),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Follow: get notified when it changes status, ships, or gets an official answer. */
|
|
108
|
+
async createFeedbackFollow({ auth, number }) {
|
|
109
|
+
return await this.web.request({
|
|
110
|
+
method: 'PUT', auth,
|
|
111
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/follows/@me'),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Unfollow. Sticks: voting again later does not re-follow. */
|
|
115
|
+
async deleteFeedbackFollow({ auth, number }) {
|
|
116
|
+
return await this.web.request({
|
|
117
|
+
method: 'DELETE', auth,
|
|
118
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/follows/@me'),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The thread: the official staff answer first, then comments oldest first with their replies (one level).
|
|
123
|
+
* Staff notes (`internal`) only reach staff. A deleted comment shows as `deleted: true` with no body while it
|
|
124
|
+
* still has replies.
|
|
125
|
+
*/
|
|
126
|
+
async getFeedbackComments({ auth, number }) {
|
|
127
|
+
return await this.web.request({
|
|
128
|
+
method: 'GET', auth,
|
|
129
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/comments'),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Comment, or reply with `parentId` (a top-level comment only). `uploadIds` from createFeedbackUpload. 10/minute. Returns the thread. */
|
|
133
|
+
async createFeedbackComment({ auth, number, comment }) {
|
|
134
|
+
return await this.web.request({
|
|
135
|
+
method: 'POST', auth,
|
|
136
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/comments'),
|
|
137
|
+
body: comment,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Edit your own comment. Returns the thread. */
|
|
141
|
+
async updateFeedbackComment({ auth, commentId, body }) {
|
|
142
|
+
return await this.web.request({
|
|
143
|
+
method: 'PATCH', auth,
|
|
144
|
+
endpoint: this.web.qp('/public/feedback/comments/' + commentId),
|
|
145
|
+
body: { body },
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Delete your own comment (its replies stay). Returns the thread. */
|
|
149
|
+
async deleteFeedbackComment({ auth, commentId }) {
|
|
150
|
+
return await this.web.request({
|
|
151
|
+
method: 'DELETE', auth,
|
|
152
|
+
endpoint: this.web.qp('/public/feedback/comments/' + commentId),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** React to a post. Answers the post's reactions. `reactions_off` (404) when the switch is off. */
|
|
156
|
+
async createFeedbackPostReaction({ auth, number, kind }) {
|
|
157
|
+
return await this.web.request({
|
|
158
|
+
method: 'PUT', auth,
|
|
159
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/reactions/' + kind),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async deleteFeedbackPostReaction({ auth, number, kind }) {
|
|
163
|
+
return await this.web.request({
|
|
164
|
+
method: 'DELETE', auth,
|
|
165
|
+
endpoint: this.web.qp('/public/feedback/posts/' + number + '/reactions/' + kind),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async createFeedbackCommentReaction({ auth, commentId, kind }) {
|
|
169
|
+
return await this.web.request({
|
|
170
|
+
method: 'PUT', auth,
|
|
171
|
+
endpoint: this.web.qp('/public/feedback/comments/' + commentId + '/reactions/' + kind),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async deleteFeedbackCommentReaction({ auth, commentId, kind }) {
|
|
175
|
+
return await this.web.request({
|
|
176
|
+
method: 'DELETE', auth,
|
|
177
|
+
endpoint: this.web.qp('/public/feedback/comments/' + commentId + '/reactions/' + kind),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Upload one image (field `image`: png, jpeg, webp or gif, up to 8 MB). It comes back as webp, at most
|
|
182
|
+
* 2000px, with its metadata (GPS, camera) removed. Put `url` in the markdown as `` and send
|
|
183
|
+
* `uploadId` in `uploadIds` when saving the post or comment; unattached uploads are deleted after 24h.
|
|
184
|
+
* 20 per day. Refusals: `uploads_off`, `image_too_large`, `unsupported_image`, `invalid_image`, `upload_quota`.
|
|
185
|
+
*/
|
|
186
|
+
async createFeedbackUpload({ auth, formData }) {
|
|
187
|
+
return await this.web.request({
|
|
188
|
+
method: 'POST', auth,
|
|
189
|
+
endpoint: this.web.qp('/public/feedback/uploads'),
|
|
190
|
+
body: formData,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/** The roadmap: planned, in progress, and shipped in the last 30 days (older ones are on the timeline). */
|
|
194
|
+
async getFeedbackRoadmap({ auth } = {}) {
|
|
195
|
+
return await this.web.request({
|
|
196
|
+
method: 'GET', auth,
|
|
197
|
+
endpoint: this.web.qp('/public/feedback/roadmap'),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Shipped posts, newest first, each with the public changelog entry it shipped in (if staff linked one). */
|
|
201
|
+
async getFeedbackTimeline({ auth, ...query } = {}) {
|
|
202
|
+
return await this.web.request({
|
|
203
|
+
method: 'GET', auth,
|
|
204
|
+
endpoint: this.web.qp('/public/feedback/timeline', query),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** Posts staff featured for the top of the feedback page (up to 10, in their order). */
|
|
208
|
+
async getFeedbackFeatured({ auth } = {}) {
|
|
209
|
+
return await this.web.request({
|
|
210
|
+
method: 'GET', auth,
|
|
211
|
+
endpoint: this.web.qp('/public/feedback/featured'),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The inbox (the bell): newest first. Visibility is checked when you read it, so a post that has gone
|
|
216
|
+
* private is simply not there. Page with `before` = the previous page's `next`.
|
|
217
|
+
*/
|
|
218
|
+
async getFeedbackNotifications({ auth, ...query }) {
|
|
219
|
+
return await this.web.request({
|
|
220
|
+
method: 'GET', auth,
|
|
221
|
+
endpoint: this.web.qp('/public/feedback/users/@me/notifications', query),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/** The bell's number. Counts up to 100 (`capped` then true - show "99+"). */
|
|
225
|
+
async getFeedbackNotificationCount({ auth }) {
|
|
226
|
+
return await this.web.request({
|
|
227
|
+
method: 'GET', auth,
|
|
228
|
+
endpoint: this.web.qp('/public/feedback/users/@me/notifications/unread'),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** Mark notifications read: the given `ids`, or all of them when omitted. */
|
|
232
|
+
async createFeedbackNotificationReads({ auth, ids }) {
|
|
233
|
+
return await this.web.request({
|
|
234
|
+
method: 'POST', auth,
|
|
235
|
+
endpoint: this.web.qp('/public/feedback/users/@me/notifications/reads'),
|
|
236
|
+
body: ids ? { ids } : {},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/** DM settings. `dmBlocked`: Discord refused a DM (closed DMs) - switching DMs on again clears it. `dmsAvailable` false = DMs are off for everyone. */
|
|
240
|
+
async getFeedbackSettings({ auth }) {
|
|
241
|
+
return await this.web.request({
|
|
242
|
+
method: 'GET', auth,
|
|
243
|
+
endpoint: this.web.qp('/public/feedback/users/@me/settings'),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/** Turn the update digest DM on or off (off by default; at most one DM per 15 minutes, 10 a day). */
|
|
247
|
+
async updateFeedbackSettings({ auth, dmEnabled }) {
|
|
248
|
+
return await this.web.request({
|
|
249
|
+
method: 'PATCH', auth,
|
|
250
|
+
endpoint: this.web.qp('/public/feedback/users/@me/settings'),
|
|
251
|
+
body: { dmEnabled },
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/** The "my feedback" page: my posts, what I voted on, what I follow (100 each, newest first). */
|
|
255
|
+
async getMyFeedback({ auth }) {
|
|
256
|
+
return await this.web.request({
|
|
257
|
+
method: 'GET', auth,
|
|
258
|
+
endpoint: this.web.qp('/public/feedback/users/@me'),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
exports.APIFeedback = APIFeedback;
|
|
263
|
+
/** Mirrors `FeedbackAudienceEnum` in Noti-v3. A post's audience is the narrower of its board's and its own. */
|
|
264
|
+
exports.FEEDBACK_AUDIENCES = ['public', 'premium', 'beta', 'staff', 'authorAndStaff'];
|
|
265
|
+
/** Mirrors `FeedbackStatusEnum`. The roadmap columns are planned / inProgress / done. */
|
|
266
|
+
exports.FEEDBACK_STATUSES = ['open', 'underReview', 'planned', 'inProgress', 'done', 'declined'];
|
|
267
|
+
/** Why an action was refused. Mirrors `FEEDBACK_REFUSALS` in Noti-v3's `src/modules/feedback/visibility.ts`. */
|
|
268
|
+
exports.FEEDBACK_REFUSALS = [
|
|
269
|
+
'login_required', 'not_visible', 'banned', 'account_too_new', 'board_archived', 'posting_closed', 'voting_locked', 'comments_locked',
|
|
270
|
+
];
|
|
271
|
+
/** Mirrors `FEEDBACK_REACTION_KINDS` in Noti-v3. */
|
|
272
|
+
exports.FEEDBACK_REACTION_KINDS = ['heart', 'thumbsUp', 'thumbsDown', 'fire'];
|
|
273
|
+
/** Mirrors `FEEDBACK_NOTIFICATION_KINDS` in Noti-v3. `merged`: the post you followed was merged into `post`. */
|
|
274
|
+
exports.FEEDBACK_NOTIFICATION_KINDS = ['statusChanged', 'shipped', 'officialAnswer', 'reply', 'merged', 'pollStarted'];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { WebDataManager } from '../core/manager';
|
|
2
|
+
import { CancelOutWebResponses, WebResponse } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Reposition Emojis - reorder a server's emojis in Discord's emoji picker.
|
|
5
|
+
*
|
|
6
|
+
* Discord has no reorder API: the picker lists a server's emojis oldest upload first. So Noti re-uploads the emojis
|
|
7
|
+
* that have to move, in the new order, and deletes the originals. **Every re-uploaded emoji gets a new id**: Noti
|
|
8
|
+
* updates its own settings (role panels, starboards, message buttons, `<:name:id>` in message text) and re-renders
|
|
9
|
+
* posted role panels, but emojis in old chat messages and other bots' settings may show as broken.
|
|
10
|
+
*
|
|
11
|
+
* Only what must move is re-uploaded: emojis at the start of the new order that are already in age order keep their
|
|
12
|
+
* place. Integration emojis (`managed`) and greyed-out ones (`available: false`) can never move - pin them.
|
|
13
|
+
*
|
|
14
|
+
* Free servers: one reposition per 7 days (`nextFreeAt`). Premium: unlimited (`unlimited`). Starting one needs a top.gg
|
|
15
|
+
* vote (403 `vote_required`, Premium skips it) and Manage Expressions for both the caller and Noti. Behind the
|
|
16
|
+
* `repositionEmojis` launch flag: every method answers 403 `not_available` until it is beta/public for the caller.
|
|
17
|
+
*/
|
|
18
|
+
export declare class APIGuildEmojis {
|
|
19
|
+
private web;
|
|
20
|
+
constructor(web: WebDataManager);
|
|
21
|
+
/** The server's emojis in picker order, its slot limits, the weekly cap, permissions, and the current / last job. */
|
|
22
|
+
getGuildEmojis({ auth, guildId }: GuildEmojisFunctionsInput['getGuildEmojis']): Promise<WebResponse<GuildEmojisData>>;
|
|
23
|
+
/**
|
|
24
|
+
* Start a reposition. `order` is EVERY emoji id in the server, in the new order. Runs in the background - poll
|
|
25
|
+
* `getEmojiReposition` for progress (large servers can take hours: Discord limits emoji uploads).
|
|
26
|
+
*
|
|
27
|
+
* Errors (`error.code`): 400 `invalid_body` / `mismatch` (the list changed - reload) / `noop` / `managed_locked` /
|
|
28
|
+
* `unavailable_locked` (`error.ids`) / `over_limit`; 403 `not_available` / `vote_required` / `not_manager` /
|
|
29
|
+
* `missing_permissions`; 409 `running`; 429 `weekly_limit` (`error.resetsAt`, `error.premiumUrl`); 503
|
|
30
|
+
* `guild_unavailable`.
|
|
31
|
+
*/
|
|
32
|
+
createEmojiReposition({ auth, guildId, order }: GuildEmojisFunctionsInput['createEmojiReposition']): Promise<WebResponse<{
|
|
33
|
+
job: EmojiRepositionJob;
|
|
34
|
+
}>>;
|
|
35
|
+
/** The current or last job (kept 7 days after it ends), or null. */
|
|
36
|
+
getEmojiReposition({ auth, guildId }: GuildEmojisFunctionsInput['getEmojiReposition']): Promise<WebResponse<{
|
|
37
|
+
job: EmojiRepositionJob | null;
|
|
38
|
+
}>>;
|
|
39
|
+
}
|
|
40
|
+
export type GuildEmoji = {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
animated: boolean;
|
|
44
|
+
/** Integration (Twitch / YouTube) emoji: cannot be moved. */
|
|
45
|
+
managed: boolean;
|
|
46
|
+
/** False when greyed out (over the limit after a boost-level drop): cannot be moved. */
|
|
47
|
+
available: boolean;
|
|
48
|
+
/** Restricted to roles; the re-upload keeps the restriction. */
|
|
49
|
+
roleLocked: boolean;
|
|
50
|
+
url: string;
|
|
51
|
+
};
|
|
52
|
+
export type GuildEmojisData = {
|
|
53
|
+
/** Picker order: oldest upload first. */
|
|
54
|
+
emojis: GuildEmoji[];
|
|
55
|
+
/** Static and animated each have `max` slots. */
|
|
56
|
+
limits: {
|
|
57
|
+
static: number;
|
|
58
|
+
animated: number;
|
|
59
|
+
max: number;
|
|
60
|
+
};
|
|
61
|
+
canManage: {
|
|
62
|
+
bot: boolean;
|
|
63
|
+
member: boolean;
|
|
64
|
+
};
|
|
65
|
+
/** Premium: no weekly cap. */
|
|
66
|
+
unlimited: boolean;
|
|
67
|
+
/** Free servers: when the next reposition is allowed (ms), or null when one is free now. */
|
|
68
|
+
nextFreeAt: number | null;
|
|
69
|
+
job: EmojiRepositionJob | null;
|
|
70
|
+
};
|
|
71
|
+
/** One emoji's progress. `skipped` = someone deleted it during the job; `failed` carries `error`. */
|
|
72
|
+
export type EmojiRepositionStep = 'pending' | 'backedUp' | 'creating' | 'created' | 'deleting' | 'done' | 'failed' | 'skipped';
|
|
73
|
+
export type EmojiRepositionJob = {
|
|
74
|
+
jobId: string;
|
|
75
|
+
guildId: string;
|
|
76
|
+
startedBy: string;
|
|
77
|
+
startedAt: number;
|
|
78
|
+
status: 'backing-up' | 'moving' | 'repointing' | 'done' | 'failed';
|
|
79
|
+
/** Emojis that kept their place. */
|
|
80
|
+
keep: string[];
|
|
81
|
+
/** Emojis being re-uploaded, in order. `newId` once its copy exists. */
|
|
82
|
+
emojis: Array<{
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
animated: boolean;
|
|
86
|
+
roles: string[];
|
|
87
|
+
step: EmojiRepositionStep;
|
|
88
|
+
newId?: string;
|
|
89
|
+
deletedFirst?: boolean;
|
|
90
|
+
backupKey?: string;
|
|
91
|
+
error?: string;
|
|
92
|
+
}>;
|
|
93
|
+
movedAny: boolean;
|
|
94
|
+
/** Why it failed, e.g. `backup_too_large:<id>`, `emoji_down:<id>`, `repoint_failed`. */
|
|
95
|
+
failure?: string;
|
|
96
|
+
finishedAt?: number;
|
|
97
|
+
};
|
|
98
|
+
export type GuildEmojisFunctionsInput = {
|
|
99
|
+
'getGuildEmojis': {
|
|
100
|
+
auth: string;
|
|
101
|
+
guildId: string;
|
|
102
|
+
};
|
|
103
|
+
'createEmojiReposition': {
|
|
104
|
+
auth: string;
|
|
105
|
+
guildId: string;
|
|
106
|
+
order: string[];
|
|
107
|
+
};
|
|
108
|
+
'getEmojiReposition': {
|
|
109
|
+
auth: string;
|
|
110
|
+
guildId: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export type GuildEmojisReturnTypes = {
|
|
114
|
+
'getGuildEmojisRaw': Awaited<ReturnType<APIGuildEmojis['getGuildEmojis']>>;
|
|
115
|
+
'getGuildEmojisSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildEmojis['getGuildEmojis']>>>;
|
|
116
|
+
'createEmojiRepositionRaw': Awaited<ReturnType<APIGuildEmojis['createEmojiReposition']>>;
|
|
117
|
+
'createEmojiRepositionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildEmojis['createEmojiReposition']>>>;
|
|
118
|
+
'getEmojiRepositionRaw': Awaited<ReturnType<APIGuildEmojis['getEmojiReposition']>>;
|
|
119
|
+
'getEmojiRepositionSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildEmojis['getEmojiReposition']>>>;
|
|
120
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APIGuildEmojis = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Reposition Emojis - reorder a server's emojis in Discord's emoji picker.
|
|
6
|
+
*
|
|
7
|
+
* Discord has no reorder API: the picker lists a server's emojis oldest upload first. So Noti re-uploads the emojis
|
|
8
|
+
* that have to move, in the new order, and deletes the originals. **Every re-uploaded emoji gets a new id**: Noti
|
|
9
|
+
* updates its own settings (role panels, starboards, message buttons, `<:name:id>` in message text) and re-renders
|
|
10
|
+
* posted role panels, but emojis in old chat messages and other bots' settings may show as broken.
|
|
11
|
+
*
|
|
12
|
+
* Only what must move is re-uploaded: emojis at the start of the new order that are already in age order keep their
|
|
13
|
+
* place. Integration emojis (`managed`) and greyed-out ones (`available: false`) can never move - pin them.
|
|
14
|
+
*
|
|
15
|
+
* Free servers: one reposition per 7 days (`nextFreeAt`). Premium: unlimited (`unlimited`). Starting one needs a top.gg
|
|
16
|
+
* vote (403 `vote_required`, Premium skips it) and Manage Expressions for both the caller and Noti. Behind the
|
|
17
|
+
* `repositionEmojis` launch flag: every method answers 403 `not_available` until it is beta/public for the caller.
|
|
18
|
+
*/
|
|
19
|
+
class APIGuildEmojis {
|
|
20
|
+
web;
|
|
21
|
+
constructor(web) {
|
|
22
|
+
this.web = web;
|
|
23
|
+
}
|
|
24
|
+
/** The server's emojis in picker order, its slot limits, the weekly cap, permissions, and the current / last job. */
|
|
25
|
+
async getGuildEmojis({ auth, guildId }) {
|
|
26
|
+
return await this.web.request({
|
|
27
|
+
method: 'GET', auth,
|
|
28
|
+
endpoint: this.web.qp(`/data/guild/${guildId}/emojis`),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Start a reposition. `order` is EVERY emoji id in the server, in the new order. Runs in the background - poll
|
|
33
|
+
* `getEmojiReposition` for progress (large servers can take hours: Discord limits emoji uploads).
|
|
34
|
+
*
|
|
35
|
+
* Errors (`error.code`): 400 `invalid_body` / `mismatch` (the list changed - reload) / `noop` / `managed_locked` /
|
|
36
|
+
* `unavailable_locked` (`error.ids`) / `over_limit`; 403 `not_available` / `vote_required` / `not_manager` /
|
|
37
|
+
* `missing_permissions`; 409 `running`; 429 `weekly_limit` (`error.resetsAt`, `error.premiumUrl`); 503
|
|
38
|
+
* `guild_unavailable`.
|
|
39
|
+
*/
|
|
40
|
+
async createEmojiReposition({ auth, guildId, order }) {
|
|
41
|
+
return await this.web.request({
|
|
42
|
+
method: 'POST', auth,
|
|
43
|
+
endpoint: this.web.qp(`/data/guild/${guildId}/emojis/repositions`),
|
|
44
|
+
body: { order },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** The current or last job (kept 7 days after it ends), or null. */
|
|
48
|
+
async getEmojiReposition({ auth, guildId }) {
|
|
49
|
+
return await this.web.request({
|
|
50
|
+
method: 'GET', auth,
|
|
51
|
+
endpoint: this.web.qp(`/data/guild/${guildId}/emojis/repositions/current`),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.APIGuildEmojis = APIGuildEmojis;
|
package/dist/classes/polls.d.ts
CHANGED
|
@@ -86,6 +86,8 @@ export type SitePollOption = {
|
|
|
86
86
|
optionId: string;
|
|
87
87
|
label: string;
|
|
88
88
|
description: string | null;
|
|
89
|
+
/** The feedback post this option stands for - link to it. Null when none, or while that post is not public. */
|
|
90
|
+
postNumber: number | null;
|
|
89
91
|
};
|
|
90
92
|
export type SitePollResults = {
|
|
91
93
|
/** In display order. */
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CancelOutWebResponses } from '../types';
|
|
2
|
+
import { WebDataManager } from '../core/manager';
|
|
3
|
+
export declare class APISocials {
|
|
4
|
+
private web;
|
|
5
|
+
constructor(web: WebDataManager);
|
|
6
|
+
/** The user's linked accounts per platform, with the limit and whether YouTube is available to them yet. */
|
|
7
|
+
getSocials({ auth }: SocialsFunctionsInput['getSocials']): Promise<import("../types").WebResponse<SocialAccountsView>>;
|
|
8
|
+
/**
|
|
9
|
+
* Link an X or Club account by its handle (`@name`, a profile link, or the bare name). Typed links are unverified.
|
|
10
|
+
* Errors: 400 invalid handle, 404 no such account, 409 limit / taken / locked, 403 banned or not available, 429 too
|
|
11
|
+
* fast, 503 the platform could not be reached (try again - it is not "not found").
|
|
12
|
+
*/
|
|
13
|
+
createSocial({ auth, platform, username }: SocialsFunctionsInput['createSocial']): Promise<import("../types").WebResponse<{
|
|
14
|
+
account: {
|
|
15
|
+
dbId: string;
|
|
16
|
+
created: boolean;
|
|
17
|
+
};
|
|
18
|
+
view: SocialAccountsView | null;
|
|
19
|
+
}>>;
|
|
20
|
+
/** Make an account the platform's primary. 409 while the platform is locked (User Premium ended with extras). */
|
|
21
|
+
updateSocial({ auth, accountId }: SocialsFunctionsInput['updateSocial']): Promise<import("../types").WebResponse<SocialAccountsView | null>>;
|
|
22
|
+
/** Unlink an account. Always allowed. Unlinking the primary makes the oldest remaining one primary. */
|
|
23
|
+
deleteSocial({ auth, accountId }: SocialsFunctionsInput['deleteSocial']): Promise<import("../types").WebResponse<SocialAccountsView | null>>;
|
|
24
|
+
}
|
|
25
|
+
export type SocialPlatform = 'youtube' | 'twitter' | 'club';
|
|
26
|
+
export type UsernameSocialPlatform = 'twitter' | 'club';
|
|
27
|
+
/** `ok` room left, `full` at the limit, `over` = User Premium ended with extra accounts: locked until unlinked down. */
|
|
28
|
+
export type SocialAccountState = 'ok' | 'full' | 'over';
|
|
29
|
+
export type SocialAccount = {
|
|
30
|
+
dbId: string;
|
|
31
|
+
username: string;
|
|
32
|
+
displayName: string | null;
|
|
33
|
+
/** true = proven by login (YouTube); false = typed handle (X, Club). */
|
|
34
|
+
verified: boolean;
|
|
35
|
+
isPrimary: boolean;
|
|
36
|
+
};
|
|
37
|
+
export type SocialAccountsView = {
|
|
38
|
+
/** Developers only for now (`latestYoutube`). Hide every YouTube control when false. */
|
|
39
|
+
canYoutube: boolean;
|
|
40
|
+
/** Accounts allowed per platform: 1 free, 3 with User Premium. */
|
|
41
|
+
limit: number;
|
|
42
|
+
platforms: {
|
|
43
|
+
platform: SocialPlatform;
|
|
44
|
+
state: SocialAccountState;
|
|
45
|
+
accounts: SocialAccount[];
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
48
|
+
export type SocialsFunctionsInput = {
|
|
49
|
+
'getSocials': {
|
|
50
|
+
auth: string;
|
|
51
|
+
};
|
|
52
|
+
'createSocial': {
|
|
53
|
+
auth: string;
|
|
54
|
+
platform: UsernameSocialPlatform;
|
|
55
|
+
username: string;
|
|
56
|
+
};
|
|
57
|
+
'updateSocial': {
|
|
58
|
+
auth: string;
|
|
59
|
+
accountId: string;
|
|
60
|
+
};
|
|
61
|
+
'deleteSocial': {
|
|
62
|
+
auth: string;
|
|
63
|
+
accountId: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export type SocialsGetReturnTypes = {
|
|
67
|
+
'getSocialsRaw': Awaited<ReturnType<APISocials['getSocials']>>;
|
|
68
|
+
'getSocialsSuccess': CancelOutWebResponses<Awaited<ReturnType<APISocials['getSocials']>>>;
|
|
69
|
+
'createSocialRaw': Awaited<ReturnType<APISocials['createSocial']>>;
|
|
70
|
+
'createSocialSuccess': CancelOutWebResponses<Awaited<ReturnType<APISocials['createSocial']>>>;
|
|
71
|
+
'updateSocialRaw': Awaited<ReturnType<APISocials['updateSocial']>>;
|
|
72
|
+
'updateSocialSuccess': CancelOutWebResponses<Awaited<ReturnType<APISocials['updateSocial']>>>;
|
|
73
|
+
'deleteSocialRaw': Awaited<ReturnType<APISocials['deleteSocial']>>;
|
|
74
|
+
'deleteSocialSuccess': CancelOutWebResponses<Awaited<ReturnType<APISocials['deleteSocial']>>>;
|
|
75
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APISocials = void 0;
|
|
4
|
+
// Linked YouTube / X / Club accounts (Noti-v3 phases/latest/). Behind the `latest` launch flag - every route 403s while it
|
|
5
|
+
// is hidden for the user. 1 account per platform free, 3 with User Premium (the `MultipleSocialAccounts` user bit); one
|
|
6
|
+
// primary per platform, which /latest uses when no account is picked.
|
|
7
|
+
class APISocials {
|
|
8
|
+
web;
|
|
9
|
+
constructor(web) {
|
|
10
|
+
this.web = web;
|
|
11
|
+
}
|
|
12
|
+
/** The user's linked accounts per platform, with the limit and whether YouTube is available to them yet. */
|
|
13
|
+
async getSocials({ auth }) {
|
|
14
|
+
return await this.web.request({
|
|
15
|
+
method: 'GET', auth,
|
|
16
|
+
endpoint: this.web.qp('/@me/socials'),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Link an X or Club account by its handle (`@name`, a profile link, or the bare name). Typed links are unverified.
|
|
21
|
+
* Errors: 400 invalid handle, 404 no such account, 409 limit / taken / locked, 403 banned or not available, 429 too
|
|
22
|
+
* fast, 503 the platform could not be reached (try again - it is not "not found").
|
|
23
|
+
*/
|
|
24
|
+
async createSocial({ auth, platform, username }) {
|
|
25
|
+
return await this.web.request({
|
|
26
|
+
method: 'POST', auth, body: { username },
|
|
27
|
+
endpoint: this.web.qp('/@me/socials/' + platform),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Make an account the platform's primary. 409 while the platform is locked (User Premium ended with extras). */
|
|
31
|
+
async updateSocial({ auth, accountId }) {
|
|
32
|
+
return await this.web.request({
|
|
33
|
+
method: 'PATCH', auth, body: { isPrimary: true },
|
|
34
|
+
endpoint: this.web.qp('/@me/socials/accounts/' + accountId),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Unlink an account. Always allowed. Unlinking the primary makes the oldest remaining one primary. */
|
|
38
|
+
async deleteSocial({ auth, accountId }) {
|
|
39
|
+
return await this.web.request({
|
|
40
|
+
method: 'DELETE', auth,
|
|
41
|
+
endpoint: this.web.qp('/@me/socials/accounts/' + accountId),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.APISocials = APISocials;
|
package/dist/core/manager.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { APIGuildBounties } from '../classes/guildBounties';
|
|
|
15
15
|
import { APIGuildAssistant } from '../classes/guildAssistant';
|
|
16
16
|
import { APIGuildAutoRoles } from '../classes/guildAutoRoles';
|
|
17
17
|
import { APIGuildBoosts } from '../classes/guildBoosts';
|
|
18
|
+
import { APIGuildEmojis } from '../classes/guildEmojis';
|
|
18
19
|
import { APIGuildMembers } from '../classes/guildMembers';
|
|
19
20
|
import { APIGuildHealth } from '../classes/guildHealth';
|
|
20
21
|
import { APIGuildInfluencerPanel } from '../classes/guildInfluencerPanel';
|
|
@@ -40,12 +41,15 @@ import { APIPublicDrops } from '../classes/publicDrops';
|
|
|
40
41
|
import { APIPolls } from '../classes/polls';
|
|
41
42
|
import { APIAnnouncements } from '../classes/announcements';
|
|
42
43
|
import { APIAdminPolls } from '../classes/adminPolls';
|
|
44
|
+
import { APIFeedback } from '../classes/feedback';
|
|
45
|
+
import { APIAdminFeedback } from '../classes/adminFeedback';
|
|
43
46
|
import { APIAdminAnnouncements } from '../classes/adminAnnouncements';
|
|
44
47
|
import { APIGuildFishing } from '../classes/guildFishing';
|
|
45
48
|
import { APIAdminFishing } from '../classes/adminFishing';
|
|
46
49
|
import { APIAdmin } from '../classes/admin';
|
|
47
50
|
import { APIVods } from '../classes/vods';
|
|
48
51
|
import { APIUser } from '../classes/user';
|
|
52
|
+
import { APISocials } from '../classes/socials';
|
|
49
53
|
import { APIMe } from '../classes/@me';
|
|
50
54
|
export declare class WebDataManager {
|
|
51
55
|
url: string;
|
|
@@ -54,6 +58,7 @@ export declare class WebDataManager {
|
|
|
54
58
|
} | undefined;
|
|
55
59
|
readonly me: APIMe;
|
|
56
60
|
readonly user: APIUser;
|
|
61
|
+
readonly socials: APISocials;
|
|
57
62
|
readonly emotes: APIEmotes;
|
|
58
63
|
readonly vods: APIVods;
|
|
59
64
|
readonly files: APIFiles;
|
|
@@ -68,6 +73,8 @@ export declare class WebDataManager {
|
|
|
68
73
|
readonly announcements: APIAnnouncements;
|
|
69
74
|
readonly adminPolls: APIAdminPolls;
|
|
70
75
|
readonly adminAnnouncements: APIAdminAnnouncements;
|
|
76
|
+
readonly feedback: APIFeedback;
|
|
77
|
+
readonly adminFeedback: APIAdminFeedback;
|
|
71
78
|
readonly other: APIOther;
|
|
72
79
|
readonly guild: APIGuild;
|
|
73
80
|
readonly teams: APITeams;
|
|
@@ -84,6 +91,7 @@ export declare class WebDataManager {
|
|
|
84
91
|
readonly guildAssistant: APIGuildAssistant;
|
|
85
92
|
readonly guildAutoRoles: APIGuildAutoRoles;
|
|
86
93
|
readonly guildBoosts: APIGuildBoosts;
|
|
94
|
+
readonly guildEmojis: APIGuildEmojis;
|
|
87
95
|
readonly guildMembers: APIGuildMembers;
|
|
88
96
|
readonly guildHealth: APIGuildHealth;
|
|
89
97
|
readonly guildInfluencerPanel: APIGuildInfluencerPanel;
|