@notidotbot/noti-api-client 1.9.62 → 1.9.64

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.
Files changed (37) hide show
  1. package/dist/classes/adminAnnouncements.d.ts +108 -0
  2. package/dist/classes/adminAnnouncements.js +52 -0
  3. package/dist/classes/adminPolls.d.ts +111 -0
  4. package/dist/classes/adminPolls.js +59 -0
  5. package/dist/classes/announcements.d.ts +68 -0
  6. package/dist/classes/announcements.js +45 -0
  7. package/dist/classes/guildGiveaway.d.ts +28 -1
  8. package/dist/classes/guildGiveaway.js +14 -0
  9. package/dist/classes/guildInfluencerPanel.d.ts +266 -0
  10. package/dist/classes/guildInfluencerPanel.js +98 -0
  11. package/dist/classes/guildPlatform.d.ts +15 -3
  12. package/dist/classes/polls.d.ts +117 -0
  13. package/dist/classes/polls.js +62 -0
  14. package/dist/classes/publicDrops.d.ts +147 -0
  15. package/dist/classes/publicDrops.js +54 -0
  16. package/dist/classes/publicGiveaways.d.ts +152 -0
  17. package/dist/classes/publicGiveaways.js +80 -0
  18. package/dist/core/manager.d.ts +14 -0
  19. package/dist/core/manager.js +14 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.js +15 -1
  22. package/dist/other/bitfields.d.ts +17 -2
  23. package/dist/other/bitfields.js +15 -0
  24. package/dist/other/features.d.ts +1 -1
  25. package/dist/other/features.js +6 -0
  26. package/dist/other/giveawayClaims.d.ts +23 -0
  27. package/dist/other/giveawayClaims.js +33 -0
  28. package/dist/other/zod/giveaways.zod.d.ts +19 -0
  29. package/dist/other/zod/giveaways.zod.js +12 -0
  30. package/dist/other/zod/guild.zod.d.ts +2 -0
  31. package/dist/other/zod/guild.zod.js +2 -0
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +1 -1
  34. package/prisma/generated/ts-prisma.d.ts +303 -58
  35. package/prisma/schema/models/giveaways.prisma +43 -0
  36. package/prisma/schema/models/guild.prisma +3 -0
  37. package/prisma/schema/schema.prisma +6 -0
@@ -0,0 +1,266 @@
1
+ import { WebDataManager } from '../core/manager';
2
+ import { CancelOutWebResponses, WebResponse } from '../types';
3
+ /**
4
+ * Influencer Panel - members holding a role the admin chose add THEIR OWN stream from a panel posted in
5
+ * Discord, and Noti posts their lives, videos and posts with the server's settings.
6
+ *
7
+ * The admin sets, per platform, the channel and ping role (and a second channel/role for videos or posts
8
+ * where the platform has them), the live role, a log channel, a "verified" role given while a member has an
9
+ * active stream, and how many accounts one member may add per platform. Influencer streams count toward the
10
+ * server's own per-platform limit; the per-member cap never raises it.
11
+ *
12
+ * A member who loses every required role, or leaves, has their streams PAUSED (the creator is untracked and
13
+ * the slot freed); getting the role back resumes them while there is room. A stream that cannot come back
14
+ * stays paused with reason `limit`.
15
+ *
16
+ * Tiers: free - one required role, cap 1. Premium - several roles, cap up to 3, approvals, member text.
17
+ * Premium Plus - the full panel editor, the Live Now board, webhook identity, cap up to 5. `data.access` from
18
+ * `getGuildInfluencerPanel` says what the server may set; a setting above it is refused with 403.
19
+ *
20
+ * Behind the `influencerPanel` launch flag: every method answers 403 until it is beta/public for the caller,
21
+ * and 403 when the server's plan lacks the Influencer Panel.
22
+ */
23
+ export declare class APIGuildInfluencerPanel {
24
+ private web;
25
+ constructor(web: WebDataManager);
26
+ /** The setup (`panel` null = never set up), what the plan allows, the platforms on offer and entry counts. */
27
+ getGuildInfluencerPanel({ auth, guildId }: GuildInfluencerPanelFunctionsInput['getGuildInfluencerPanel']): Promise<WebResponse<GuildInfluencerPanelData>>;
28
+ /**
29
+ * Change the setup, field by field - omit one to leave it unchanged. `platforms` upserts the platforms
30
+ * listed and leaves the others alone.
31
+ *
32
+ * 400 names the problem: a bad id, a platform switched on without a channel, a setting the platform does
33
+ * not have, or a channel/role Noti cannot use. **403** for a setting above the plan. Turning a Premium
34
+ * setting OFF is always accepted.
35
+ *
36
+ * After saving, every active influencer stream is updated to the new settings (and paused where its
37
+ * platform went off), a posted panel and board are redrawn, and switching the panel back on resumes what
38
+ * switching it off paused. `applied`, `panelError` and `boardError` report that.
39
+ */
40
+ updateGuildInfluencerPanel({ auth, guildId, ...body }: GuildInfluencerPanelFunctionsInput['updateGuildInfluencerPanel']): Promise<WebResponse<GuildInfluencerPanelUpdateData>>;
41
+ /** Post the panel to `panelChannelId`, or redraw it where it already is. 400 with the reason when Discord or a permission refuses. */
42
+ createGuildInfluencerPanelMessage({ auth, guildId }: GuildInfluencerPanelFunctionsInput['createGuildInfluencerPanelMessage']): Promise<WebResponse<GuildInfluencerPanelData>>;
43
+ /** Delete the posted panel message. The setup and every stream stay. */
44
+ deleteGuildInfluencerPanelMessage({ auth, guildId }: GuildInfluencerPanelFunctionsInput['deleteGuildInfluencerPanelMessage']): Promise<WebResponse<GuildInfluencerPanelData>>;
45
+ /** Members' streams, newest first (at most 500). `status: 'pending'` is the approval queue. */
46
+ getGuildInfluencerEntries({ auth, guildId, status }: GuildInfluencerPanelFunctionsInput['getGuildInfluencerEntries']): Promise<WebResponse<{
47
+ entries: GuildInfluencerEntry[];
48
+ }>>;
49
+ /**
50
+ * Act on one stream: `pause` (active only), `resume` (paused only - goes through the server's limit
51
+ * again), `approve` or `deny` (pending only; `reason` is sent to the member). 404 when it is not this
52
+ * server's, 409 while the member's streams are being changed.
53
+ */
54
+ updateGuildInfluencerEntry({ auth, guildId, entryId, ...body }: GuildInfluencerPanelFunctionsInput['updateGuildInfluencerEntry']): Promise<WebResponse<{
55
+ entry: GuildInfluencerEntry;
56
+ }>>;
57
+ /** Remove a stream: the creator is untracked and the member is told in the log channel. */
58
+ deleteGuildInfluencerEntry({ auth, guildId, entryId }: GuildInfluencerPanelFunctionsInput['deleteGuildInfluencerEntry']): Promise<WebResponse<{
59
+ entryId: string;
60
+ }>>;
61
+ }
62
+ /** Platforms the panel offers. */
63
+ export type InfluencerPlatform = 'kick' | 'twitch' | 'youtube' | 'tiktok' | 'rumble' | 'twitter' | 'club';
64
+ /** Every `InfluencerPlatform`, in the order the API lists them. */
65
+ export declare const INFLUENCER_PLATFORMS: readonly InfluencerPlatform[];
66
+ export type InfluencerEntryStatus = 'pending' | 'active' | 'paused' | 'denied';
67
+ /** Why a stream is paused: lost the role, left, panel/platform switched off, paused by an admin, server limit full on resume. */
68
+ export type InfluencerPausedReason = 'role' | 'left' | 'panelOff' | 'manual' | 'limit';
69
+ export type InfluencerEntryAction = 'pause' | 'resume' | 'approve' | 'deny';
70
+ export type GuildInfluencerPlatformSettings = {
71
+ platform: InfluencerPlatform;
72
+ enabled: boolean;
73
+ /** Lives (posts on Club). */
74
+ channelId: string | null;
75
+ pingRoleId: string | null;
76
+ /** Videos (YouTube, TikTok, Rumble) or posts (X). */
77
+ secondaryChannelId: string | null;
78
+ /** YouTube, Rumble and X only - TikTok uses `pingRoleId` for both. */
79
+ secondaryPingRoleId: string | null;
80
+ };
81
+ /** The panel message's look. `title` and `description` on every plan; the rest is Premium Plus. */
82
+ export type GuildInfluencerPanelDesign = {
83
+ title?: string;
84
+ description?: string;
85
+ /** 0x000000-0xFFFFFF. */
86
+ color?: number;
87
+ /** https only. */
88
+ imageUrl?: string;
89
+ footer?: string;
90
+ addLabel?: string;
91
+ mineLabel?: string;
92
+ addEmoji?: string;
93
+ mineEmoji?: string;
94
+ };
95
+ export type GuildInfluencerPanel = {
96
+ dbId: string;
97
+ guildId: string;
98
+ enabled: boolean;
99
+ /** Any one of them lets a member use the panel. More than one is Premium. */
100
+ requiredRoleIds: string[];
101
+ logChannelId: string | null;
102
+ /** Streams one member may add per platform. */
103
+ perMemberLimit: number;
104
+ liveRoleId: string | null;
105
+ verifiedRoleId: string | null;
106
+ /**
107
+ * Creator Managers (every plan): besides Manage Server, members with one of these roles may approve /
108
+ * deny requests from the log channel and use `/influencer add|remove @member` in Discord.
109
+ */
110
+ managerRoleIds: string[];
111
+ /** Premium. */
112
+ requireApproval: boolean;
113
+ /** Premium. The member's own line replaces the random message. */
114
+ allowMemberText: boolean;
115
+ /** Adds `{member}` (the member's mention) to their notifications. */
116
+ showMemberTag: boolean;
117
+ /**
118
+ * Premium Plus + CustomWebhooks. Influencer notifications (live, offline, video, posts) post through a
119
+ * webhook with `webhookUsername` / `webhookAvatarUrl`. A creator's own custom-message webhook still wins;
120
+ * this wins over the server's default. Applies only while a name or an avatar is set.
121
+ */
122
+ useWebhook: boolean;
123
+ /** 1-80 characters, no "discord" or "clyde". Takes placeholders, e.g. `{streamerName}`. */
124
+ webhookUsername: string | null;
125
+ /** https only. */
126
+ webhookAvatarUrl: string | null;
127
+ panelChannelId: string | null;
128
+ panelMessageId: string | null;
129
+ panelDesign: GuildInfluencerPanelDesign | null;
130
+ /** Premium Plus. */
131
+ liveBoardChannelId: string | null;
132
+ liveBoardMessageId: string | null;
133
+ createdAt: string;
134
+ updatedAt: string;
135
+ platforms: GuildInfluencerPlatformSettings[];
136
+ };
137
+ /** What the server's plan allows. */
138
+ export type GuildInfluencerPanelAccess = {
139
+ tier: 'free' | 'premium' | 'plus';
140
+ maxRequiredRoles: number;
141
+ maxPerMemberLimit: number;
142
+ approvals: boolean;
143
+ memberText: boolean;
144
+ /** The full panel editor (beyond title and description). */
145
+ panelDesign: boolean;
146
+ liveBoard: boolean;
147
+ /** Webhook identity: Premium Plus AND CustomWebhooks. */
148
+ webhook: boolean;
149
+ };
150
+ /** Which settings a platform has, so the page draws the right fields. */
151
+ export type GuildInfluencerPlatformMeta = {
152
+ platform: InfluencerPlatform;
153
+ secondary: 'video' | 'post' | null;
154
+ secondaryRole: boolean;
155
+ };
156
+ export type GuildInfluencerPanelData = {
157
+ panel: GuildInfluencerPanel | null;
158
+ access: GuildInfluencerPanelAccess;
159
+ platforms: GuildInfluencerPlatformMeta[];
160
+ counts: Record<InfluencerEntryStatus, number>;
161
+ };
162
+ export type GuildInfluencerPanelUpdateData = GuildInfluencerPanelData & {
163
+ /** Active streams updated, paused (platform went off) and resumed (panel switched back on). */
164
+ applied: {
165
+ updated: number;
166
+ paused: number;
167
+ resumed: number;
168
+ };
169
+ /** Why a posted panel could not be redrawn, or null. */
170
+ panelError: string | null;
171
+ /** Why the Live Now board could not be redrawn, or null. */
172
+ boardError: string | null;
173
+ };
174
+ export type GuildInfluencerEntry = {
175
+ dbId: string;
176
+ guildId: string;
177
+ /** The member's Discord id. */
178
+ userId: string;
179
+ platform: InfluencerPlatform;
180
+ streamerUserName: string;
181
+ status: InfluencerEntryStatus;
182
+ pausedReason: InfluencerPausedReason | null;
183
+ deniedReason: string | null;
184
+ /** The member's own line (Premium). */
185
+ note: string | null;
186
+ /** Who approved or denied it. */
187
+ decidedBy: string | null;
188
+ /** Rumble only: 'channel' or 'user'. */
189
+ rumbleType: string | null;
190
+ createdAt: string;
191
+ updatedAt: string;
192
+ };
193
+ /** The setup's editable fields. */
194
+ export type GuildInfluencerPanelUpdate = {
195
+ enabled?: boolean;
196
+ requiredRoleIds?: string[];
197
+ /** Up to 10 roles. Checked against the server: a role that no longer exists is refused. */
198
+ managerRoleIds?: string[];
199
+ logChannelId?: string | null;
200
+ perMemberLimit?: number;
201
+ liveRoleId?: string | null;
202
+ verifiedRoleId?: string | null;
203
+ requireApproval?: boolean;
204
+ allowMemberText?: boolean;
205
+ showMemberTag?: boolean;
206
+ panelChannelId?: string | null;
207
+ panelDesign?: GuildInfluencerPanelDesign | null;
208
+ liveBoardChannelId?: string | null;
209
+ useWebhook?: boolean;
210
+ webhookUsername?: string | null;
211
+ webhookAvatarUrl?: string | null;
212
+ platforms?: Array<{
213
+ platform: InfluencerPlatform;
214
+ } & Partial<Omit<GuildInfluencerPlatformSettings, 'platform'>>>;
215
+ };
216
+ export type GuildInfluencerPanelFunctionsInput = {
217
+ 'getGuildInfluencerPanel': {
218
+ auth: string;
219
+ guildId: string;
220
+ };
221
+ 'updateGuildInfluencerPanel': {
222
+ auth: string;
223
+ guildId: string;
224
+ } & GuildInfluencerPanelUpdate;
225
+ 'createGuildInfluencerPanelMessage': {
226
+ auth: string;
227
+ guildId: string;
228
+ };
229
+ 'deleteGuildInfluencerPanelMessage': {
230
+ auth: string;
231
+ guildId: string;
232
+ };
233
+ 'getGuildInfluencerEntries': {
234
+ auth: string;
235
+ guildId: string;
236
+ status?: InfluencerEntryStatus;
237
+ };
238
+ 'updateGuildInfluencerEntry': {
239
+ auth: string;
240
+ guildId: string;
241
+ entryId: string;
242
+ action: InfluencerEntryAction;
243
+ reason?: string;
244
+ };
245
+ 'deleteGuildInfluencerEntry': {
246
+ auth: string;
247
+ guildId: string;
248
+ entryId: string;
249
+ };
250
+ };
251
+ export type GuildInfluencerPanelReturnTypes = {
252
+ 'getGuildInfluencerPanelRaw': Awaited<ReturnType<APIGuildInfluencerPanel['getGuildInfluencerPanel']>>;
253
+ 'getGuildInfluencerPanelSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['getGuildInfluencerPanel']>>>;
254
+ 'updateGuildInfluencerPanelRaw': Awaited<ReturnType<APIGuildInfluencerPanel['updateGuildInfluencerPanel']>>;
255
+ 'updateGuildInfluencerPanelSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['updateGuildInfluencerPanel']>>>;
256
+ 'createGuildInfluencerPanelMessageRaw': Awaited<ReturnType<APIGuildInfluencerPanel['createGuildInfluencerPanelMessage']>>;
257
+ 'createGuildInfluencerPanelMessageSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['createGuildInfluencerPanelMessage']>>>;
258
+ 'deleteGuildInfluencerPanelMessageRaw': Awaited<ReturnType<APIGuildInfluencerPanel['deleteGuildInfluencerPanelMessage']>>;
259
+ 'deleteGuildInfluencerPanelMessageSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['deleteGuildInfluencerPanelMessage']>>>;
260
+ 'getGuildInfluencerEntriesRaw': Awaited<ReturnType<APIGuildInfluencerPanel['getGuildInfluencerEntries']>>;
261
+ 'getGuildInfluencerEntriesSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['getGuildInfluencerEntries']>>>;
262
+ 'updateGuildInfluencerEntryRaw': Awaited<ReturnType<APIGuildInfluencerPanel['updateGuildInfluencerEntry']>>;
263
+ 'updateGuildInfluencerEntrySuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['updateGuildInfluencerEntry']>>>;
264
+ 'deleteGuildInfluencerEntryRaw': Awaited<ReturnType<APIGuildInfluencerPanel['deleteGuildInfluencerEntry']>>;
265
+ 'deleteGuildInfluencerEntrySuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildInfluencerPanel['deleteGuildInfluencerEntry']>>>;
266
+ };
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INFLUENCER_PLATFORMS = exports.APIGuildInfluencerPanel = void 0;
4
+ /**
5
+ * Influencer Panel - members holding a role the admin chose add THEIR OWN stream from a panel posted in
6
+ * Discord, and Noti posts their lives, videos and posts with the server's settings.
7
+ *
8
+ * The admin sets, per platform, the channel and ping role (and a second channel/role for videos or posts
9
+ * where the platform has them), the live role, a log channel, a "verified" role given while a member has an
10
+ * active stream, and how many accounts one member may add per platform. Influencer streams count toward the
11
+ * server's own per-platform limit; the per-member cap never raises it.
12
+ *
13
+ * A member who loses every required role, or leaves, has their streams PAUSED (the creator is untracked and
14
+ * the slot freed); getting the role back resumes them while there is room. A stream that cannot come back
15
+ * stays paused with reason `limit`.
16
+ *
17
+ * Tiers: free - one required role, cap 1. Premium - several roles, cap up to 3, approvals, member text.
18
+ * Premium Plus - the full panel editor, the Live Now board, webhook identity, cap up to 5. `data.access` from
19
+ * `getGuildInfluencerPanel` says what the server may set; a setting above it is refused with 403.
20
+ *
21
+ * Behind the `influencerPanel` launch flag: every method answers 403 until it is beta/public for the caller,
22
+ * and 403 when the server's plan lacks the Influencer Panel.
23
+ */
24
+ class APIGuildInfluencerPanel {
25
+ web;
26
+ constructor(web) {
27
+ this.web = web;
28
+ }
29
+ /** The setup (`panel` null = never set up), what the plan allows, the platforms on offer and entry counts. */
30
+ async getGuildInfluencerPanel({ auth, guildId }) {
31
+ return await this.web.request({
32
+ method: 'GET', auth,
33
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel`),
34
+ });
35
+ }
36
+ /**
37
+ * Change the setup, field by field - omit one to leave it unchanged. `platforms` upserts the platforms
38
+ * listed and leaves the others alone.
39
+ *
40
+ * 400 names the problem: a bad id, a platform switched on without a channel, a setting the platform does
41
+ * not have, or a channel/role Noti cannot use. **403** for a setting above the plan. Turning a Premium
42
+ * setting OFF is always accepted.
43
+ *
44
+ * After saving, every active influencer stream is updated to the new settings (and paused where its
45
+ * platform went off), a posted panel and board are redrawn, and switching the panel back on resumes what
46
+ * switching it off paused. `applied`, `panelError` and `boardError` report that.
47
+ */
48
+ async updateGuildInfluencerPanel({ auth, guildId, ...body }) {
49
+ return await this.web.request({
50
+ method: 'PATCH', auth,
51
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel`),
52
+ body,
53
+ });
54
+ }
55
+ /** Post the panel to `panelChannelId`, or redraw it where it already is. 400 with the reason when Discord or a permission refuses. */
56
+ async createGuildInfluencerPanelMessage({ auth, guildId }) {
57
+ return await this.web.request({
58
+ method: 'POST', auth,
59
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel/message`),
60
+ });
61
+ }
62
+ /** Delete the posted panel message. The setup and every stream stay. */
63
+ async deleteGuildInfluencerPanelMessage({ auth, guildId }) {
64
+ return await this.web.request({
65
+ method: 'DELETE', auth,
66
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel/message`),
67
+ });
68
+ }
69
+ /** Members' streams, newest first (at most 500). `status: 'pending'` is the approval queue. */
70
+ async getGuildInfluencerEntries({ auth, guildId, status }) {
71
+ return await this.web.request({
72
+ method: 'GET', auth,
73
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel/entries`, { status }),
74
+ });
75
+ }
76
+ /**
77
+ * Act on one stream: `pause` (active only), `resume` (paused only - goes through the server's limit
78
+ * again), `approve` or `deny` (pending only; `reason` is sent to the member). 404 when it is not this
79
+ * server's, 409 while the member's streams are being changed.
80
+ */
81
+ async updateGuildInfluencerEntry({ auth, guildId, entryId, ...body }) {
82
+ return await this.web.request({
83
+ method: 'PATCH', auth,
84
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel/entries/${entryId}`),
85
+ body,
86
+ });
87
+ }
88
+ /** Remove a stream: the creator is untracked and the member is told in the log channel. */
89
+ async deleteGuildInfluencerEntry({ auth, guildId, entryId }) {
90
+ return await this.web.request({
91
+ method: 'DELETE', auth,
92
+ endpoint: this.web.qp(`/data/guild/${guildId}/influencer-panel/entries/${entryId}`),
93
+ });
94
+ }
95
+ }
96
+ exports.APIGuildInfluencerPanel = APIGuildInfluencerPanel;
97
+ /** Every `InfluencerPlatform`, in the order the API lists them. */
98
+ exports.INFLUENCER_PLATFORMS = ['kick', 'twitch', 'youtube', 'tiktok', 'rumble', 'twitter', 'club'];
@@ -132,9 +132,21 @@ type ResolvesUsersForLiveRole = 'kick' | 'twitch' | 'rumble' | 'tiktok' | 'youtu
132
132
  * `string[]`, not with `[]`. Discriminate on the element - `typeof first === 'string'` - rather than
133
133
  * on length, because an empty array is a legitimate member of both arms.
134
134
  */
135
- export type GuildSingleStreamerWithFormatedRole<T extends AllPlatforms> = T extends ServedPlatform ? T extends ResolvesUsersForLiveRole ? Omit<GuildSingleStreamer<T>, 'usersForLiveRole'> & {
135
+ /**
136
+ * A creator a community member added from the Influencer Panel: the member, and their panel entry (for
137
+ * `guildInfluencerPanel.updateGuildInfluencerEntry` / `deleteGuildInfluencerEntry`). `null` on every
138
+ * creator an admin added. Only on the two GET routes - create/update answer with the row alone.
139
+ */
140
+ export type GuildStreamerInfluencerLink = {
141
+ userId: string;
142
+ entryId: string;
143
+ };
144
+ type WithInfluencerLink = {
145
+ influencer: GuildStreamerInfluencerLink | null;
146
+ };
147
+ export type GuildSingleStreamerWithFormatedRole<T extends AllPlatforms> = T extends ServedPlatform ? (T extends ResolvesUsersForLiveRole ? Omit<GuildSingleStreamer<T>, 'usersForLiveRole'> & {
136
148
  usersForLiveRole: UsersForLiveRole | string[];
137
- } : GuildSingleStreamer<T> : never;
149
+ } : GuildSingleStreamer<T>) & WithInfluencerLink : never;
138
150
  /**
139
151
  * The platforms `GET /data/guild/:guildId/:platform` can actually answer.
140
152
  *
@@ -191,7 +203,7 @@ export type Platformstreamers<T extends boolean, P extends AllPlatforms> = {
191
203
  * The zod type under-claims instead (it also drops the relation FK scalars), and under-claiming
192
204
  * cannot be read by accident.
193
205
  */
194
- streamers: P extends ServedPlatform ? (T extends true ? Pick<GuildSingleStreamer<P>, Extract<keyof GuildSingleStreamer<P>, ShortViewKeySets[P]>> : GuildSingleStreamer<P>)[] : never;
206
+ streamers: P extends ServedPlatform ? ((T extends true ? Pick<GuildSingleStreamer<P>, Extract<keyof GuildSingleStreamer<P>, ShortViewKeySets[P]>> : GuildSingleStreamer<P>) & WithInfluencerLink)[] : never;
195
207
  currentTotal: number;
196
208
  maxLimit: number;
197
209
  premium: {
@@ -0,0 +1,117 @@
1
+ import { CancelOutWebResponses } from '../types';
2
+ import { WebDataManager } from '../core/manager';
3
+ /**
4
+ * Site polls - developer-run polls on the dashboard ("which feature next?").
5
+ *
6
+ * ── WHO SEES WHAT ──────────────────────────────────────────────────────────
7
+ * `listPolls` needs no session. Everything else needs a logged-in user (any Discord account), so pass `auth`.
8
+ * Behind the `sitePolls` launch flag: while it is hidden for the caller the list is EMPTY (not 403) and a
9
+ * single poll 404s. Draft and scheduled polls are 404 here; developers manage them through `adminPolls`.
10
+ *
11
+ * ── RESULTS ────────────────────────────────────────────────────────────────
12
+ * Each poll sets `resultsVisibility`: `always`, `afterVote` (once this user has voted, or the poll closed) or
13
+ * `afterClose`. The server never sends counts the viewer may not see, so `results` is null until then - do
14
+ * not compute or guess them on the page.
15
+ *
16
+ * Percent is a share of VOTES on a single-choice poll and a share of VOTERS on a multi-choice one (so those
17
+ * can sum past 100). The public list is cached up to 15s, so counts there can trail by that much.
18
+ */
19
+ export declare class APIPolls {
20
+ private web;
21
+ constructor(web: WebDataManager);
22
+ /** Open polls first, then ones closed in the last 30 days. No session. */
23
+ listPolls(): Promise<import("../types").WebResponse<SitePollList>>;
24
+ /** One poll, plus where the logged-in user stands on it. */
25
+ getPoll({ auth, pollId }: PollsFunctionsInput['getPoll']): Promise<import("../types").WebResponse<SitePollView>>;
26
+ /**
27
+ * Cast or change the logged-in user's vote: `optionIds` is the full pick (1 to `maxChoices`), replacing
28
+ * any earlier one. A refusal is `{ status: 400 | 404 | 409 | 429, error: { code } }`, `code` from
29
+ * `SitePollErrorCode`. Re-sending the same pick is not an error even when changes are locked. 10/minute.
30
+ */
31
+ setPollVote({ auth, pollId, optionIds }: PollsFunctionsInput['setPollVote']): Promise<import("../types").WebResponse<SitePollView>>;
32
+ /** Withdraw the logged-in user's vote. Only on polls with `allowVoteChange`, while open. */
33
+ deletePollVote({ auth, pollId }: PollsFunctionsInput['deletePollVote']): Promise<import("../types").WebResponse<SitePollView>>;
34
+ }
35
+ export type PollsFunctionsInput = {
36
+ 'getPoll': {
37
+ auth: string;
38
+ pollId: string;
39
+ };
40
+ 'setPollVote': {
41
+ auth: string;
42
+ pollId: string;
43
+ optionIds: string[];
44
+ };
45
+ 'deletePollVote': {
46
+ auth: string;
47
+ pollId: string;
48
+ };
49
+ };
50
+ export type PollsGetReturnTypes = {
51
+ 'listPollsRaw': Awaited<ReturnType<APIPolls['listPolls']>>;
52
+ 'listPollsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIPolls['listPolls']>>>;
53
+ 'getPollRaw': Awaited<ReturnType<APIPolls['getPoll']>>;
54
+ 'getPollSuccess': CancelOutWebResponses<Awaited<ReturnType<APIPolls['getPoll']>>>;
55
+ 'setPollVoteRaw': Awaited<ReturnType<APIPolls['setPollVote']>>;
56
+ 'setPollVoteSuccess': CancelOutWebResponses<Awaited<ReturnType<APIPolls['setPollVote']>>>;
57
+ 'deletePollVoteRaw': Awaited<ReturnType<APIPolls['deletePollVote']>>;
58
+ 'deletePollVoteSuccess': CancelOutWebResponses<Awaited<ReturnType<APIPolls['deletePollVote']>>>;
59
+ };
60
+ /** Why a vote or withdrawal was refused. Mirrors `SITE_POLL_REFUSALS` in Noti-v3's `src/modules/sitePolls.ts`. */
61
+ export declare const SITE_POLL_REFUSALS: readonly ["not_open", "closed", "no_choice", "too_many_choices", "invalid_option", "vote_locked"];
62
+ export type SitePollRefusal = typeof SITE_POLL_REFUSALS[number];
63
+ /** Every `error.code` the vote routes return: the refusals, plus `busy` (a vote of theirs is still saving) and `rate_limited`. */
64
+ export type SitePollErrorCode = SitePollRefusal | 'busy' | 'rate_limited';
65
+ export type SitePollState = 'draft' | 'scheduled' | 'open' | 'closed';
66
+ export type SitePollResultsVisibility = 'always' | 'afterVote' | 'afterClose';
67
+ /** One poll as the site shows it. Mirrors `SitePollCard` in Noti-v3. */
68
+ export type SitePoll = {
69
+ pollId: string;
70
+ title: string;
71
+ /** Markdown. */
72
+ description: string | null;
73
+ state: SitePollState;
74
+ /** 1 = single choice. */
75
+ maxChoices: number;
76
+ allowVoteChange: boolean;
77
+ resultsVisibility: SitePollResultsVisibility;
78
+ /** ISO. */
79
+ opensAt: string | null;
80
+ /** ISO. */
81
+ closesAt: string | null;
82
+ /** In display order. */
83
+ options: SitePollOption[];
84
+ };
85
+ export type SitePollOption = {
86
+ optionId: string;
87
+ label: string;
88
+ description: string | null;
89
+ };
90
+ export type SitePollResults = {
91
+ /** In display order. */
92
+ options: Array<{
93
+ optionId: string;
94
+ votes: number;
95
+ percent: number;
96
+ }>;
97
+ totalVotes: number;
98
+ totalVoters: number;
99
+ };
100
+ export type SitePollViewer = {
101
+ votedOptionIds: string[];
102
+ /** Can vote now (open, and not voted yet or changes allowed). */
103
+ canVote: boolean;
104
+ canChangeVote: boolean;
105
+ resultsVisible: boolean;
106
+ };
107
+ export type SitePollList = {
108
+ polls: Array<{
109
+ poll: SitePoll;
110
+ results: SitePollResults | null;
111
+ }>;
112
+ };
113
+ export type SitePollView = {
114
+ poll: SitePoll;
115
+ viewer: SitePollViewer;
116
+ results: SitePollResults | null;
117
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SITE_POLL_REFUSALS = exports.APIPolls = void 0;
4
+ // Data.
5
+ /**
6
+ * Site polls - developer-run polls on the dashboard ("which feature next?").
7
+ *
8
+ * ── WHO SEES WHAT ──────────────────────────────────────────────────────────
9
+ * `listPolls` needs no session. Everything else needs a logged-in user (any Discord account), so pass `auth`.
10
+ * Behind the `sitePolls` launch flag: while it is hidden for the caller the list is EMPTY (not 403) and a
11
+ * single poll 404s. Draft and scheduled polls are 404 here; developers manage them through `adminPolls`.
12
+ *
13
+ * ── RESULTS ────────────────────────────────────────────────────────────────
14
+ * Each poll sets `resultsVisibility`: `always`, `afterVote` (once this user has voted, or the poll closed) or
15
+ * `afterClose`. The server never sends counts the viewer may not see, so `results` is null until then - do
16
+ * not compute or guess them on the page.
17
+ *
18
+ * Percent is a share of VOTES on a single-choice poll and a share of VOTERS on a multi-choice one (so those
19
+ * can sum past 100). The public list is cached up to 15s, so counts there can trail by that much.
20
+ */
21
+ class APIPolls {
22
+ web;
23
+ constructor(web) {
24
+ this.web = web;
25
+ }
26
+ /** Open polls first, then ones closed in the last 30 days. No session. */
27
+ async listPolls() {
28
+ return await this.web.request({
29
+ method: 'GET',
30
+ endpoint: this.web.qp('/public/polls'),
31
+ });
32
+ }
33
+ /** One poll, plus where the logged-in user stands on it. */
34
+ async getPoll({ auth, pollId }) {
35
+ return await this.web.request({
36
+ method: 'GET', auth,
37
+ endpoint: this.web.qp('/public/polls/' + pollId),
38
+ });
39
+ }
40
+ /**
41
+ * Cast or change the logged-in user's vote: `optionIds` is the full pick (1 to `maxChoices`), replacing
42
+ * any earlier one. A refusal is `{ status: 400 | 404 | 409 | 429, error: { code } }`, `code` from
43
+ * `SitePollErrorCode`. Re-sending the same pick is not an error even when changes are locked. 10/minute.
44
+ */
45
+ async setPollVote({ auth, pollId, optionIds }) {
46
+ return await this.web.request({
47
+ method: 'PUT', auth,
48
+ endpoint: this.web.qp('/public/polls/' + pollId + '/votes/@me'),
49
+ body: { optionIds },
50
+ });
51
+ }
52
+ /** Withdraw the logged-in user's vote. Only on polls with `allowVoteChange`, while open. */
53
+ async deletePollVote({ auth, pollId }) {
54
+ return await this.web.request({
55
+ method: 'DELETE', auth,
56
+ endpoint: this.web.qp('/public/polls/' + pollId + '/votes/@me'),
57
+ });
58
+ }
59
+ }
60
+ exports.APIPolls = APIPolls;
61
+ /** Why a vote or withdrawal was refused. Mirrors `SITE_POLL_REFUSALS` in Noti-v3's `src/modules/sitePolls.ts`. */
62
+ exports.SITE_POLL_REFUSALS = ['not_open', 'closed', 'no_choice', 'too_many_choices', 'invalid_option', 'vote_locked'];