@notidotbot/noti-api-client 1.9.39 → 1.9.41

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.
@@ -1,357 +1,45 @@
1
- import { GuildClipForwardCreateSchema, GuildClipForwardZod } from '../other/prisma';
1
+ import { GuildClipForward, GuildClipForwardCreateSchema, GuildClipForwardZod } from '../other/prisma';
2
2
  import { CancelOutWebResponses, DeepPartial } from '../types';
3
3
  import { WebDataManager } from '../core/manager';
4
+ /** The platforms clip forwarding supports. Caps, addons and rows are all counted per platform. */
5
+ export type ClipForwardPlatform = 'kick' | 'twitch';
6
+ /** One platform's allowance: tier default + purchased addon, and how much of it is used. */
7
+ export type ClipForwardCap = {
8
+ currentTotal: number;
9
+ maxLimit: number;
10
+ };
11
+ export type GuildClipForwardsData = {
12
+ forwards: GuildClipForward[];
13
+ /**
14
+ * PER PLATFORM, because the allowance is. `defaultKickClips` and `defaultTwitchClips` are separate
15
+ * tier numbers with separate addons (`additionalKickClips` / `additionalTwitchClips`), so a single
16
+ * pair would be wrong for whichever platform you were rendering.
17
+ *
18
+ * The server computes this with the same helper the create route enforces with, so a UI that
19
+ * believes there is room will not be refused - render the Add control off `currentTotal < maxLimit`
20
+ * rather than counting `forwards` yourself.
21
+ */
22
+ limits: Record<ClipForwardPlatform, ClipForwardCap>;
23
+ /** Which tier the allowance came from, so an upsell can name it. */
24
+ premium: {
25
+ tierId: string;
26
+ tierName: string;
27
+ };
28
+ };
4
29
  export declare class APIGuildClipForward {
5
30
  private web;
6
31
  constructor(web: WebDataManager);
7
- getGuildClipForwards({ auth, guildId }: GuildClipForwardFunctionsInput['getGuildClipForwards']): Promise<import("../types").WebResponse<({
8
- customMessages: ({
9
- clipForward: {
10
- dbId: string;
11
- guildId: string;
12
- platform: import("@prisma/client").$Enums.PlatformEnum;
13
- channelId: string | null;
14
- streamerUserName: string;
15
- usePerStreamerEmbeds: boolean | null;
16
- showNotifyButton: boolean | null;
17
- pingRoleId: string | null;
18
- clipForwardId: string;
19
- minViews: number | null;
20
- minDuration: number | null;
21
- maxPerCycle: number | null;
22
- allowMature: boolean | null;
23
- includeOffline: boolean | null;
24
- whitelistClippers: string[];
25
- blacklistClippers: string[];
26
- whitelistCategories: string[];
27
- blacklistCategories: string[];
28
- lastClipAt: Date | null;
29
- lastClipId: string | null;
30
- } | null;
31
- webhook: ({
32
- cfCustomMessage: ({
33
- clipForward: {
34
- dbId: string;
35
- guildId: string;
36
- platform: import("@prisma/client").$Enums.PlatformEnum;
37
- channelId: string | null;
38
- streamerUserName: string;
39
- usePerStreamerEmbeds: boolean | null;
40
- showNotifyButton: boolean | null;
41
- pingRoleId: string | null;
42
- clipForwardId: string;
43
- minViews: number | null;
44
- minDuration: number | null;
45
- maxPerCycle: number | null;
46
- allowMature: boolean | null;
47
- includeOffline: boolean | null;
48
- whitelistClippers: string[];
49
- blacklistClippers: string[];
50
- whitelistCategories: string[];
51
- blacklistCategories: string[];
52
- lastClipAt: Date | null;
53
- lastClipId: string | null;
54
- } | null;
55
- embed: ({
56
- fields: ({
57
- cfMessageEmbed: {
58
- dbId: string;
59
- title: string | null;
60
- description: string;
61
- url: string | null;
62
- color: string | null;
63
- footer: string | null;
64
- footerIcon: string | null;
65
- image: string | null;
66
- thumbnail: string | null;
67
- author: string | null;
68
- authorIcon: string | null;
69
- cfCustomMessageId: string;
70
- } | null;
71
- } & {
72
- name: string;
73
- dbId: string;
74
- value: string | null;
75
- inline: boolean | null;
76
- cfMessageEmbedId: string;
77
- })[];
78
- } & {
79
- dbId: string;
80
- title: string | null;
81
- description: string;
82
- url: string | null;
83
- color: string | null;
84
- footer: string | null;
85
- footerIcon: string | null;
86
- image: string | null;
87
- thumbnail: string | null;
88
- author: string | null;
89
- authorIcon: string | null;
90
- cfCustomMessageId: string;
91
- }) | null;
92
- buttons: {
93
- dbId: string;
94
- title: string;
95
- url: string | null;
96
- emojiUnicodeOrId: string | null;
97
- cfCustomMessageId: string;
98
- }[];
99
- } & {
100
- dbId: string;
101
- type: import("@prisma/client").$Enums.StreamerMessageTypeEnum;
102
- content: string | null;
103
- showInviteButton: boolean | null;
104
- showWatchButton: boolean | null;
105
- clipForwardId: string;
106
- }) | null;
107
- } & {
108
- dbId: string;
109
- enabled: boolean | null;
110
- username: string | null;
111
- avatarUrl: string | null;
112
- cfCustomMessageId: string;
113
- }) | null;
114
- embed: ({
115
- fields: ({
116
- cfMessageEmbed: ({
117
- cfCustomMessage: ({
118
- clipForward: {
119
- dbId: string;
120
- guildId: string;
121
- platform: import("@prisma/client").$Enums.PlatformEnum;
122
- channelId: string | null;
123
- streamerUserName: string;
124
- usePerStreamerEmbeds: boolean | null;
125
- showNotifyButton: boolean | null;
126
- pingRoleId: string | null;
127
- clipForwardId: string;
128
- minViews: number | null;
129
- minDuration: number | null;
130
- maxPerCycle: number | null;
131
- allowMature: boolean | null;
132
- includeOffline: boolean | null;
133
- whitelistClippers: string[];
134
- blacklistClippers: string[];
135
- whitelistCategories: string[];
136
- blacklistCategories: string[];
137
- lastClipAt: Date | null;
138
- lastClipId: string | null;
139
- } | null;
140
- webhook: {
141
- dbId: string;
142
- enabled: boolean | null;
143
- username: string | null;
144
- avatarUrl: string | null;
145
- cfCustomMessageId: string;
146
- } | null;
147
- buttons: {
148
- dbId: string;
149
- title: string;
150
- url: string | null;
151
- emojiUnicodeOrId: string | null;
152
- cfCustomMessageId: string;
153
- }[];
154
- } & {
155
- dbId: string;
156
- type: import("@prisma/client").$Enums.StreamerMessageTypeEnum;
157
- content: string | null;
158
- showInviteButton: boolean | null;
159
- showWatchButton: boolean | null;
160
- clipForwardId: string;
161
- }) | null;
162
- } & {
163
- dbId: string;
164
- title: string | null;
165
- description: string;
166
- url: string | null;
167
- color: string | null;
168
- footer: string | null;
169
- footerIcon: string | null;
170
- image: string | null;
171
- thumbnail: string | null;
172
- author: string | null;
173
- authorIcon: string | null;
174
- cfCustomMessageId: string;
175
- }) | null;
176
- } & {
177
- name: string;
178
- dbId: string;
179
- value: string | null;
180
- inline: boolean | null;
181
- cfMessageEmbedId: string;
182
- })[];
183
- cfCustomMessage: ({
184
- clipForward: {
185
- dbId: string;
186
- guildId: string;
187
- platform: import("@prisma/client").$Enums.PlatformEnum;
188
- channelId: string | null;
189
- streamerUserName: string;
190
- usePerStreamerEmbeds: boolean | null;
191
- showNotifyButton: boolean | null;
192
- pingRoleId: string | null;
193
- clipForwardId: string;
194
- minViews: number | null;
195
- minDuration: number | null;
196
- maxPerCycle: number | null;
197
- allowMature: boolean | null;
198
- includeOffline: boolean | null;
199
- whitelistClippers: string[];
200
- blacklistClippers: string[];
201
- whitelistCategories: string[];
202
- blacklistCategories: string[];
203
- lastClipAt: Date | null;
204
- lastClipId: string | null;
205
- } | null;
206
- webhook: {
207
- dbId: string;
208
- enabled: boolean | null;
209
- username: string | null;
210
- avatarUrl: string | null;
211
- cfCustomMessageId: string;
212
- } | null;
213
- buttons: {
214
- dbId: string;
215
- title: string;
216
- url: string | null;
217
- emojiUnicodeOrId: string | null;
218
- cfCustomMessageId: string;
219
- }[];
220
- } & {
221
- dbId: string;
222
- type: import("@prisma/client").$Enums.StreamerMessageTypeEnum;
223
- content: string | null;
224
- showInviteButton: boolean | null;
225
- showWatchButton: boolean | null;
226
- clipForwardId: string;
227
- }) | null;
228
- } & {
229
- dbId: string;
230
- title: string | null;
231
- description: string;
232
- url: string | null;
233
- color: string | null;
234
- footer: string | null;
235
- footerIcon: string | null;
236
- image: string | null;
237
- thumbnail: string | null;
238
- author: string | null;
239
- authorIcon: string | null;
240
- cfCustomMessageId: string;
241
- }) | null;
242
- buttons: ({
243
- cfCustomMessage: ({
244
- clipForward: {
245
- dbId: string;
246
- guildId: string;
247
- platform: import("@prisma/client").$Enums.PlatformEnum;
248
- channelId: string | null;
249
- streamerUserName: string;
250
- usePerStreamerEmbeds: boolean | null;
251
- showNotifyButton: boolean | null;
252
- pingRoleId: string | null;
253
- clipForwardId: string;
254
- minViews: number | null;
255
- minDuration: number | null;
256
- maxPerCycle: number | null;
257
- allowMature: boolean | null;
258
- includeOffline: boolean | null;
259
- whitelistClippers: string[];
260
- blacklistClippers: string[];
261
- whitelistCategories: string[];
262
- blacklistCategories: string[];
263
- lastClipAt: Date | null;
264
- lastClipId: string | null;
265
- } | null;
266
- webhook: {
267
- dbId: string;
268
- enabled: boolean | null;
269
- username: string | null;
270
- avatarUrl: string | null;
271
- cfCustomMessageId: string;
272
- } | null;
273
- embed: ({
274
- fields: ({
275
- cfMessageEmbed: {
276
- dbId: string;
277
- title: string | null;
278
- description: string;
279
- url: string | null;
280
- color: string | null;
281
- footer: string | null;
282
- footerIcon: string | null;
283
- image: string | null;
284
- thumbnail: string | null;
285
- author: string | null;
286
- authorIcon: string | null;
287
- cfCustomMessageId: string;
288
- } | null;
289
- } & {
290
- name: string;
291
- dbId: string;
292
- value: string | null;
293
- inline: boolean | null;
294
- cfMessageEmbedId: string;
295
- })[];
296
- } & {
297
- dbId: string;
298
- title: string | null;
299
- description: string;
300
- url: string | null;
301
- color: string | null;
302
- footer: string | null;
303
- footerIcon: string | null;
304
- image: string | null;
305
- thumbnail: string | null;
306
- author: string | null;
307
- authorIcon: string | null;
308
- cfCustomMessageId: string;
309
- }) | null;
310
- } & {
311
- dbId: string;
312
- type: import("@prisma/client").$Enums.StreamerMessageTypeEnum;
313
- content: string | null;
314
- showInviteButton: boolean | null;
315
- showWatchButton: boolean | null;
316
- clipForwardId: string;
317
- }) | null;
318
- } & {
319
- dbId: string;
320
- title: string;
321
- url: string | null;
322
- emojiUnicodeOrId: string | null;
323
- cfCustomMessageId: string;
324
- })[];
325
- } & {
326
- dbId: string;
327
- type: import("@prisma/client").$Enums.StreamerMessageTypeEnum;
328
- content: string | null;
329
- showInviteButton: boolean | null;
330
- showWatchButton: boolean | null;
331
- clipForwardId: string;
332
- })[];
333
- } & {
334
- dbId: string;
335
- guildId: string;
336
- platform: import("@prisma/client").$Enums.PlatformEnum;
337
- channelId: string | null;
338
- streamerUserName: string;
339
- usePerStreamerEmbeds: boolean | null;
340
- showNotifyButton: boolean | null;
341
- pingRoleId: string | null;
342
- clipForwardId: string;
343
- minViews: number | null;
344
- minDuration: number | null;
345
- maxPerCycle: number | null;
346
- allowMature: boolean | null;
347
- includeOffline: boolean | null;
348
- whitelistClippers: string[];
349
- blacklistClippers: string[];
350
- whitelistCategories: string[];
351
- blacklistCategories: string[];
352
- lastClipAt: Date | null;
353
- lastClipId: string | null;
354
- })[]>>;
32
+ /**
33
+ * Every clip-forward rule for this guild, with the per-platform allowance.
34
+ *
35
+ * **BREAKING in 1.9.40**: this returned `GuildClipForward[]` directly. The rows moved to
36
+ * `data.forwards` so the allowance could travel with them - `WebResponse` is exactly
37
+ * `{ status, data }`, so there is no room for a sibling field and an array has nowhere to put one.
38
+ *
39
+ * The caps were always enforced on create; they were simply never reported, which made a capped
40
+ * feature indistinguishable from an uncapped one until the create failed.
41
+ */
42
+ getGuildClipForwards({ auth, guildId }: GuildClipForwardFunctionsInput['getGuildClipForwards']): Promise<import("../types").WebResponse<GuildClipForwardsData>>;
355
43
  getGuildClipForward({ auth, guildId, clipForwardId }: GuildClipForwardFunctionsInput['getGuildClipForward']): Promise<import("../types").WebResponse<{
356
44
  customMessages: ({
357
45
  clipForward: {
@@ -8,6 +8,16 @@ class APIGuildClipForward {
8
8
  this.web = web;
9
9
  }
10
10
  // Methods.
11
+ /**
12
+ * Every clip-forward rule for this guild, with the per-platform allowance.
13
+ *
14
+ * **BREAKING in 1.9.40**: this returned `GuildClipForward[]` directly. The rows moved to
15
+ * `data.forwards` so the allowance could travel with them - `WebResponse` is exactly
16
+ * `{ status, data }`, so there is no room for a sibling field and an array has nowhere to put one.
17
+ *
18
+ * The caps were always enforced on create; they were simply never reported, which made a capped
19
+ * feature indistinguishable from an uncapped one until the create failed.
20
+ */
11
21
  async getGuildClipForwards({ auth, guildId }) {
12
22
  return await this.web.request({
13
23
  method: 'GET', auth,
@@ -10,6 +10,33 @@ import { WebDataManager } from '../core/manager';
10
10
  export type StatChannelPlatform = 'kick' | 'twitch' | 'youtube' | 'tiktok' | 'rumble' | 'whatnot' | 'twitter';
11
11
  /** Only Kick has a follower counter. Every other model has `isLive` alone. */
12
12
  export type StatChannelKind = 'isLive' | 'followers';
13
+ /**
14
+ * What `createStatChannels` accepts, which is NOT `StatChannelKind`.
15
+ *
16
+ * `liveUpdates` is the same counter `StatChannelKind` calls `isLive`. The create body has said
17
+ * `liveUpdates` since counters were made from `POST /panel`, and the rest of the stat-channel surface
18
+ * says `isLive`; the server keeps both rather than breaking existing callers, and its RESPONSE always
19
+ * speaks `StatChannelKind`. So you send `liveUpdates` and read `isLive` back, deliberately.
20
+ */
21
+ export type StatChannelCreateType = 'liveUpdates' | 'followers';
22
+ /**
23
+ * The three counter names a guild can format.
24
+ *
25
+ * NOT `StatChannelKind` - that is which CHANNEL exists (`isLive` is one channel whose name changes
26
+ * between states), while `live` and `offline` are two templates for that same channel.
27
+ */
28
+ export type StatChannelFormatKind = 'live' | 'offline' | 'followers';
29
+ /**
30
+ * Per-platform counter name formats.
31
+ *
32
+ * A counter's name came from ONE guild-wide template, so a server watching Kick and Twitch got the
33
+ * identical name for both. An entry here overrides that for one platform; a platform with no entry
34
+ * falls back to the guild-wide template, which falls back to the bot's default.
35
+ *
36
+ * `followers` is Kick-only - it is the only platform with a follower counter at all, and the API
37
+ * refuses the key elsewhere rather than storing something no channel could use.
38
+ */
39
+ export type StatChannelFormats = Partial<Record<StatChannelPlatform, Partial<Record<StatChannelFormatKind, string>>>>;
13
40
  /**
14
41
  * Why a counter is or is not updating.
15
42
  *
@@ -50,6 +77,24 @@ export type StatChannelsData = {
50
77
  * `cluster-unreachable` and none of them says anything about the channels themselves.
51
78
  */
52
79
  inspected: boolean;
80
+ /**
81
+ * Platforms a counter can be CREATED for, which is narrower than the platforms that can hold one.
82
+ *
83
+ * Creating a counter needs the bot to resolve the creator's live state first, and only some
84
+ * platforms have a check for that - so YouTube, X and Whatnot can carry a counter that already
85
+ * exists while refusing a new one. Read this instead of hard-coding the list: it is served from the
86
+ * single constant the server also gates the create route on, and it widened once already.
87
+ *
88
+ * Absent on an API older than 1.9.40, so treat `undefined` as "Kick only".
89
+ */
90
+ canCreate?: StatChannelPlatform[];
91
+ /**
92
+ * The per-platform name formats currently stored, so an editor can render them without a second
93
+ * call. `{}` when nothing is overridden - never null.
94
+ *
95
+ * Absent on an API older than 1.9.41.
96
+ */
97
+ formats?: StatChannelFormats;
53
98
  channels: StatChannel[];
54
99
  };
55
100
  export type UpdatedStatChannel = {
@@ -71,6 +116,17 @@ export type DeletedStatChannels = {
71
116
  /** Always false. Unlinking stops the counter; the Discord channel stays theirs to remove. */
72
117
  channelDeleted: boolean;
73
118
  };
119
+ export type CreatedStatChannels = {
120
+ platform: StatChannelPlatform;
121
+ username: string;
122
+ /**
123
+ * One entry per counter created, in `updateStatChannel`'s shape.
124
+ *
125
+ * Read back from the database after creation rather than echoing the request, so the ids are the
126
+ * ones that actually landed. A caller does not need to re-`getStatChannels` to learn them.
127
+ */
128
+ channels: UpdatedStatChannel[];
129
+ };
74
130
  export type RetriedStatChannel = {
75
131
  platform: StatChannelPlatform;
76
132
  username: string;
@@ -87,6 +143,40 @@ export declare class APIGuildStatChannels {
87
143
  constructor(web: WebDataManager);
88
144
  /** Every counter this guild has configured, with why each is or is not updating. */
89
145
  getStatChannels({ auth, guildId }: GuildStatChannelsFunctionsInput['getStatChannels']): Promise<WebResponse<StatChannelsData>>;
146
+ /**
147
+ * Set the per-platform counter name formats.
148
+ *
149
+ * **REPLACES the whole map** - send it in full every time. Omit a platform to drop its override,
150
+ * send `{}` to clear every one. It does NOT merge, and that is deliberate: under merge a key once
151
+ * written could never be removed and the unset state would be unreachable after the first save.
152
+ * Same contract as fishing's `logChannelOverrides`.
153
+ *
154
+ * Requires the message-editor feature as well as stat counters - 403 without it, because storing a
155
+ * format that would never be read looks saved and does nothing.
156
+ *
157
+ * 400 on a platform that cannot have counters, on a `followers` format for anything but Kick, and
158
+ * on a format over 100 characters - refused rather than truncated, since a silently shortened
159
+ * channel name is not what anyone typed.
160
+ */
161
+ updateStatChannelFormats({ auth, guildId, formats }: GuildStatChannelsFunctionsInput['updateStatChannelFormats']): Promise<WebResponse<{
162
+ formats: StatChannelFormats;
163
+ }>>;
164
+ /**
165
+ * Create the counter channels themselves, and link them.
166
+ *
167
+ * Use this rather than making a voice channel and pointing `updateStatChannel` at it - the bot
168
+ * names the new channel from the server's own `statChannelLive` / `statFollowers` template and sets
169
+ * its permissions.
170
+ *
171
+ * **Check `canCreate` from `getStatChannels` first.** Creating needs the bot to read the creator's
172
+ * live state, and the platforms that support that are narrower than the ones that can hold a
173
+ * counter - a platform outside that list is a 400, not a silent no-op.
174
+ *
175
+ * `followers` is Kick-only; asking for it anywhere else is a 400 naming the platform. 404 when the
176
+ * creator is not tracked in this guild, 400 when the live or follower read failed, in which case
177
+ * nothing was created.
178
+ */
179
+ createStatChannels({ auth, guildId, platform, username, types }: GuildStatChannelsFunctionsInput['createStatChannels']): Promise<WebResponse<CreatedStatChannels>>;
90
180
  /**
91
181
  * Point a counter at a channel, creating it if the creator had none.
92
182
  *
@@ -114,6 +204,18 @@ export type GuildStatChannelsFunctionsInput = {
114
204
  auth: string;
115
205
  guildId: string;
116
206
  };
207
+ 'createStatChannels': {
208
+ auth: string;
209
+ guildId: string;
210
+ platform: StatChannelPlatform;
211
+ username: string;
212
+ types: StatChannelCreateType[];
213
+ };
214
+ 'updateStatChannelFormats': {
215
+ auth: string;
216
+ guildId: string;
217
+ formats: StatChannelFormats;
218
+ };
117
219
  'updateStatChannel': {
118
220
  auth: string;
119
221
  guildId: string;
@@ -140,6 +242,10 @@ export type GuildStatChannelsFunctionsInput = {
140
242
  export type GuildStatChannelsReturnTypes = {
141
243
  'getStatChannelsRaw': Awaited<ReturnType<APIGuildStatChannels['getStatChannels']>>;
142
244
  'getStatChannelsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildStatChannels['getStatChannels']>>>;
245
+ 'createStatChannelsRaw': Awaited<ReturnType<APIGuildStatChannels['createStatChannels']>>;
246
+ 'createStatChannelsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildStatChannels['createStatChannels']>>>;
247
+ 'updateStatChannelFormatsRaw': Awaited<ReturnType<APIGuildStatChannels['updateStatChannelFormats']>>;
248
+ 'updateStatChannelFormatsSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildStatChannels['updateStatChannelFormats']>>>;
143
249
  'updateStatChannelRaw': Awaited<ReturnType<APIGuildStatChannels['updateStatChannel']>>;
144
250
  'updateStatChannelSuccess': CancelOutWebResponses<Awaited<ReturnType<APIGuildStatChannels['updateStatChannel']>>>;
145
251
  'deleteStatChannelsRaw': Awaited<ReturnType<APIGuildStatChannels['deleteStatChannels']>>;
@@ -15,6 +15,50 @@ class APIGuildStatChannels {
15
15
  endpoint: this.web.qp(`/data/guild/${guildId}/stat-channels`),
16
16
  });
17
17
  }
18
+ /**
19
+ * Set the per-platform counter name formats.
20
+ *
21
+ * **REPLACES the whole map** - send it in full every time. Omit a platform to drop its override,
22
+ * send `{}` to clear every one. It does NOT merge, and that is deliberate: under merge a key once
23
+ * written could never be removed and the unset state would be unreachable after the first save.
24
+ * Same contract as fishing's `logChannelOverrides`.
25
+ *
26
+ * Requires the message-editor feature as well as stat counters - 403 without it, because storing a
27
+ * format that would never be read looks saved and does nothing.
28
+ *
29
+ * 400 on a platform that cannot have counters, on a `followers` format for anything but Kick, and
30
+ * on a format over 100 characters - refused rather than truncated, since a silently shortened
31
+ * channel name is not what anyone typed.
32
+ */
33
+ async updateStatChannelFormats({ auth, guildId, formats }) {
34
+ return await this.web.request({
35
+ method: 'PATCH', auth,
36
+ endpoint: this.web.qp(`/data/guild/${guildId}/stat-channels/formats`),
37
+ body: { formats },
38
+ });
39
+ }
40
+ /**
41
+ * Create the counter channels themselves, and link them.
42
+ *
43
+ * Use this rather than making a voice channel and pointing `updateStatChannel` at it - the bot
44
+ * names the new channel from the server's own `statChannelLive` / `statFollowers` template and sets
45
+ * its permissions.
46
+ *
47
+ * **Check `canCreate` from `getStatChannels` first.** Creating needs the bot to read the creator's
48
+ * live state, and the platforms that support that are narrower than the ones that can hold a
49
+ * counter - a platform outside that list is a 400, not a silent no-op.
50
+ *
51
+ * `followers` is Kick-only; asking for it anywhere else is a 400 naming the platform. 404 when the
52
+ * creator is not tracked in this guild, 400 when the live or follower read failed, in which case
53
+ * nothing was created.
54
+ */
55
+ async createStatChannels({ auth, guildId, platform, username, types }) {
56
+ return await this.web.request({
57
+ method: 'POST', auth,
58
+ endpoint: this.web.qp(`/data/guild/${guildId}/stat-channels/${platform}/${username}`),
59
+ body: { types },
60
+ });
61
+ }
18
62
  /**
19
63
  * Point a counter at a channel, creating it if the creator had none.
20
64
  *