@notidotbot/noti-api-client 1.9.72 → 1.9.74

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/guildStatChannels.d.ts +18 -6
  2. package/dist/classes/guildStatChannels.js +4 -3
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +8 -1
  5. package/dist/other/bitfields.d.ts +8 -2
  6. package/dist/other/bitfields.js +6 -0
  7. package/dist/other/features.d.ts +1 -1
  8. package/dist/other/features.js +2 -0
  9. package/dist/other/multiDestination.d.ts +89 -0
  10. package/dist/other/multiDestination.js +109 -0
  11. package/dist/other/templates.d.ts +180 -0
  12. package/dist/other/templates.js +194 -0
  13. package/dist/other/zod/clubStreamer.zod.d.ts +7 -0
  14. package/dist/other/zod/clubStreamer.zod.js +3 -0
  15. package/dist/other/zod/kickStreamer.zod.d.ts +17 -0
  16. package/dist/other/zod/kickStreamer.zod.js +4 -0
  17. package/dist/other/zod/rumbleStreamer.zod.d.ts +19 -0
  18. package/dist/other/zod/rumbleStreamer.zod.js +10 -0
  19. package/dist/other/zod/tiktokStreamer.zod.d.ts +18 -0
  20. package/dist/other/zod/tiktokStreamer.zod.js +4 -0
  21. package/dist/other/zod/twitchStreamer.zod.d.ts +18 -0
  22. package/dist/other/zod/twitchStreamer.zod.js +10 -0
  23. package/dist/other/zod/twitterStreamer.zod.d.ts +19 -0
  24. package/dist/other/zod/twitterStreamer.zod.js +10 -0
  25. package/dist/other/zod/youtubeStreamer.zod.d.ts +21 -0
  26. package/dist/other/zod/youtubeStreamer.zod.js +5 -0
  27. package/dist/other/zod/zod.d.ts +15 -0
  28. package/dist/other/zod/zod.js +12 -0
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/prisma/schema/models/clubStreamer.prisma +4 -0
  32. package/prisma/schema/models/kickStreamer.prisma +6 -0
  33. package/prisma/schema/models/rumbleStreamer.prisma +7 -0
  34. package/prisma/schema/models/tiktokStreamer.prisma +6 -0
  35. package/prisma/schema/models/twitchStreamer.prisma +7 -0
  36. package/prisma/schema/models/twitterStreamer.prisma +7 -0
  37. package/prisma/schema/models/youtubeStreamer.prisma +9 -1
@@ -8,7 +8,10 @@ import { WebDataManager } from '../core/manager';
8
8
  * both when either changes - a platform missing here is one the dashboard cannot configure.
9
9
  */
10
10
  export type StatChannelPlatform = 'kick' | 'twitch' | 'youtube' | 'tiktok' | 'rumble' | 'whatnot' | 'twitter';
11
- /** Only Kick has a follower counter. Every other model has `isLive` alone. */
11
+ /**
12
+ * `followers` exists on Kick (followers) and YouTube (SUBSCRIBERS, since 1.9.73 - same kind, shown to users as
13
+ * subscribers). Every other model has `isLive` alone.
14
+ */
12
15
  export type StatChannelKind = 'isLive' | 'followers';
13
16
  /**
14
17
  * What `createStatChannels` accepts, which is NOT `StatChannelKind`.
@@ -33,8 +36,8 @@ export type StatChannelFormatKind = 'live' | 'offline' | 'followers';
33
36
  * identical name for both. An entry here overrides that for one platform; a platform with no entry
34
37
  * falls back to the guild-wide template, which falls back to the bot's default.
35
38
  *
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.
39
+ * `followers` is Kick and YouTube only (YouTube's is the subscriber counter) - the API refuses the key
40
+ * elsewhere rather than storing something no channel could use.
38
41
  */
39
42
  export type StatChannelFormats = Partial<Record<StatChannelPlatform, Partial<Record<StatChannelFormatKind, string>>>>;
40
43
  /**
@@ -95,6 +98,14 @@ export type StatChannelsData = {
95
98
  * Absent on an API older than 1.9.40, so treat `undefined` as "Kick only".
96
99
  */
97
100
  canCreate?: StatChannelPlatform[];
101
+ /**
102
+ * Which counter kinds each `canCreate` platform can create. Narrower than `canCreate` alone says:
103
+ * **YouTube is in `canCreate` for a subscriber counter only** - create it with `types: ['followers']`;
104
+ * `liveUpdates` is refused, because YouTube has no positive offline signal.
105
+ *
106
+ * Absent on an API older than 1.9.73, where YouTube was not creatable at all.
107
+ */
108
+ createKinds?: Partial<Record<StatChannelPlatform, StatChannelKind[]>>;
98
109
  /**
99
110
  * The per-platform name formats currently stored, so an editor can render them without a second
100
111
  * call. `{}` when nothing is overridden - never null.
@@ -214,9 +225,10 @@ export declare class APIGuildStatChannels {
214
225
  * live state, and the platforms that support that are narrower than the ones that can hold a
215
226
  * counter - a platform outside that list is a 400, not a silent no-op.
216
227
  *
217
- * `followers` is Kick-only; asking for it anywhere else is a 400 naming the platform. 404 when the
218
- * creator is not tracked in this guild, 400 when the live or follower read failed, in which case
219
- * nothing was created.
228
+ * `followers` is Kick (followers) and YouTube (subscribers); YouTube accepts ONLY `followers` - see
229
+ * `createKinds`. Asking for an unsupported kind is a 400 naming the platform. 404 when the creator is
230
+ * not tracked in this guild, 400 when the live, follower or subscriber read failed (nothing was
231
+ * created), and 400 when a YouTube channel hides its subscriber count.
220
232
  */
221
233
  createStatChannels({ auth, guildId, platform, username, types }: GuildStatChannelsFunctionsInput['createStatChannels']): Promise<WebResponse<CreatedStatChannels>>;
222
234
  /**
@@ -48,9 +48,10 @@ class APIGuildStatChannels {
48
48
  * live state, and the platforms that support that are narrower than the ones that can hold a
49
49
  * counter - a platform outside that list is a 400, not a silent no-op.
50
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.
51
+ * `followers` is Kick (followers) and YouTube (subscribers); YouTube accepts ONLY `followers` - see
52
+ * `createKinds`. Asking for an unsupported kind is a 400 naming the platform. 404 when the creator is
53
+ * not tracked in this guild, 400 when the live, follower or subscriber read failed (nothing was
54
+ * created), and 400 when a YouTube channel hides its subscriber count.
54
55
  */
55
56
  async createStatChannels({ auth, guildId, platform, username, types }) {
56
57
  return await this.web.request({
package/dist/index.d.ts CHANGED
@@ -47,6 +47,8 @@ export { GuildMessageTypeEnum, StreamerMessageTypeEnum, PlatformEnum, $Enums } f
47
47
  export { FEATURE_NAMES, } from './other/features';
48
48
  export { OFFLINE_GRACE_CHOICES, OFFLINE_GRACE_DEFAULT, OFFLINE_GRACE_PLATFORMS, } from './other/offlineGrace';
49
49
  export type { OfflineGraceMinutes, OfflineGracePlatform } from './other/offlineGrace';
50
+ export { DESTINATION_TYPES, MAX_EXTRA_DESTINATIONS, normalizeExtraDestinations, extrasFor, readExtraPosts, hasLivePost, } from './other/multiDestination';
51
+ export type { DestinationPlatform, DestinationType, ExtraDestination, ExtraPost } from './other/multiDestination';
50
52
  export { GIVEAWAY_CLAIM_WINDOWS, GIVEAWAY_CLAIM_WINDOW_LABELS, GIVEAWAY_MAX_CLAIM_ROUNDS, GIVEAWAY_WINNER_ROLE_MAX_DAYS, GIVEAWAY_MIN_ACCOUNT_AGE_MAX_DAYS, GIVEAWAY_WINNER_ROLE_FORBIDDEN_PERMISSIONS, } from './other/giveawayClaims';
51
53
  export type { GiveawayClaimWindow, GiveawayWinnerStatus } from './other/giveawayClaims';
52
54
  export type { FeatureName, FeatureStatus, ResolvedFeature, } from './other/features';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.GIVEAWAY_WINNER_ROLE_FORBIDDEN_PERMISSIONS = exports.GIVEAWAY_MIN_ACCOUNT_AGE_MAX_DAYS = exports.GIVEAWAY_WINNER_ROLE_MAX_DAYS = exports.GIVEAWAY_MAX_CLAIM_ROUNDS = exports.GIVEAWAY_CLAIM_WINDOW_LABELS = exports.GIVEAWAY_CLAIM_WINDOWS = exports.OFFLINE_GRACE_PLATFORMS = exports.OFFLINE_GRACE_DEFAULT = exports.OFFLINE_GRACE_CHOICES = exports.FEATURE_NAMES = exports.$Enums = exports.PlatformEnum = exports.StreamerMessageTypeEnum = exports.GuildMessageTypeEnum = void 0;
17
+ exports.GIVEAWAY_WINNER_ROLE_FORBIDDEN_PERMISSIONS = exports.GIVEAWAY_MIN_ACCOUNT_AGE_MAX_DAYS = exports.GIVEAWAY_WINNER_ROLE_MAX_DAYS = exports.GIVEAWAY_MAX_CLAIM_ROUNDS = exports.GIVEAWAY_CLAIM_WINDOW_LABELS = exports.GIVEAWAY_CLAIM_WINDOWS = exports.hasLivePost = exports.readExtraPosts = exports.extrasFor = exports.normalizeExtraDestinations = exports.MAX_EXTRA_DESTINATIONS = exports.DESTINATION_TYPES = exports.OFFLINE_GRACE_PLATFORMS = exports.OFFLINE_GRACE_DEFAULT = exports.OFFLINE_GRACE_CHOICES = exports.FEATURE_NAMES = exports.$Enums = exports.PlatformEnum = exports.StreamerMessageTypeEnum = exports.GuildMessageTypeEnum = void 0;
18
18
  __exportStar(require("./core/manager"), exports);
19
19
  __exportStar(require("./types"), exports);
20
20
  __exportStar(require("./classes/@me"), exports);
@@ -73,6 +73,13 @@ var offlineGrace_1 = require("./other/offlineGrace");
73
73
  Object.defineProperty(exports, "OFFLINE_GRACE_CHOICES", { enumerable: true, get: function () { return offlineGrace_1.OFFLINE_GRACE_CHOICES; } });
74
74
  Object.defineProperty(exports, "OFFLINE_GRACE_DEFAULT", { enumerable: true, get: function () { return offlineGrace_1.OFFLINE_GRACE_DEFAULT; } });
75
75
  Object.defineProperty(exports, "OFFLINE_GRACE_PLATFORMS", { enumerable: true, get: function () { return offlineGrace_1.OFFLINE_GRACE_PLATFORMS; } });
76
+ var multiDestination_1 = require("./other/multiDestination");
77
+ Object.defineProperty(exports, "DESTINATION_TYPES", { enumerable: true, get: function () { return multiDestination_1.DESTINATION_TYPES; } });
78
+ Object.defineProperty(exports, "MAX_EXTRA_DESTINATIONS", { enumerable: true, get: function () { return multiDestination_1.MAX_EXTRA_DESTINATIONS; } });
79
+ Object.defineProperty(exports, "normalizeExtraDestinations", { enumerable: true, get: function () { return multiDestination_1.normalizeExtraDestinations; } });
80
+ Object.defineProperty(exports, "extrasFor", { enumerable: true, get: function () { return multiDestination_1.extrasFor; } });
81
+ Object.defineProperty(exports, "readExtraPosts", { enumerable: true, get: function () { return multiDestination_1.readExtraPosts; } });
82
+ Object.defineProperty(exports, "hasLivePost", { enumerable: true, get: function () { return multiDestination_1.hasLivePost; } });
76
83
  var giveawayClaims_1 = require("./other/giveawayClaims");
77
84
  Object.defineProperty(exports, "GIVEAWAY_CLAIM_WINDOWS", { enumerable: true, get: function () { return giveawayClaims_1.GIVEAWAY_CLAIM_WINDOWS; } });
78
85
  Object.defineProperty(exports, "GIVEAWAY_CLAIM_WINDOW_LABELS", { enumerable: true, get: function () { return giveawayClaims_1.GIVEAWAY_CLAIM_WINDOW_LABELS; } });
@@ -128,6 +128,8 @@ export declare const GuildFeatures: {
128
128
  InfluencerPanelPlus: bigint;
129
129
  StatusRoleKeepOnOffline: bigint;
130
130
  YouTubeReplayChannel: bigint;
131
+ LivePostUpdates: bigint;
132
+ MultiDestination: bigint;
131
133
  };
132
134
  export declare const GuildFeaturesText: Record<keyof typeof GuildFeatures, string>;
133
135
  export declare const UserFeatures: {
@@ -243,8 +245,10 @@ export declare class GuildFeaturesManager extends BitFieldManager<typeof GuildFe
243
245
  InfluencerPanelPlus: bigint;
244
246
  StatusRoleKeepOnOffline: bigint;
245
247
  YouTubeReplayChannel: bigint;
248
+ LivePostUpdates: bigint;
249
+ MultiDestination: bigint;
246
250
  };
247
- t: Record<"StickyMessages" | "LinkedRoles" | "StatusRoles" | "Starboards" | "KickClips" | "StatCounters" | "BaseSyncableRoles" | "FullSyncableRoles" | "LiveRole" | "VoteSkipping" | "KickAuditLogs" | "LeaderboardCommand" | "RefreshingLeaderboard" | "LeaderBoardSyncRoles" | "BetaAccess" | "PriorityNotifications" | "KickNotifications" | "TwitchNotifications" | "ImportEmojis" | "ImportEmojisWithSub" | "CustomWebhooks" | "AnyMessageEditor" | "SyncSocialUsernames" | "AutoDeleteNotifications" | "TestNotificationCommand" | "CustomCooldowns" | "WelcomeAndLeaveNotifications" | "NotificationPingRoleId" | "SwitchStreamerAndGuildEmbeds" | "BirthdayNotifications" | "AllowNotSendingOfflineNotifications" | "AutoStatusLiveRoles" | "RumbleNotifications" | "AllowAutoPublishNotifications" | "NotificationOverrides" | "UseCustomButtons" | "AllowVodFeatures" | "TikTokNotifications" | "ManageBranding" | "CustomStarboards" | "AutoThreadOnStarboard" | "StarboardRewardRole" | "Giveaways" | "GiveawayLogs" | "ModifyGiveawayWhileRunning" | "ScheduleGiveaways" | "GiveawayRequirements" | "GiveawayBonusEntries" | "GiveawayEntryLimit" | "DisableDoubleVoteEntry" | "YouTubeNotifications" | "RandomMessages" | "WeeklyBirthdayPanel" | "GameNotifications" | "GameCategoryRouting" | "GameAllTimeLowAlerts" | "GameChangelogs" | "GameRobloxTracking" | "FixUrlWebhook" | "FixUrlCustomRules" | "KickClipperSubscribe" | "WhatnotNotifications" | "ScheduledEventMessages" | "ScheduledMessageWebhooks" | "ReactionRoles" | "AdvancedRolePanels" | "KickClipForwarding" | "TwitchClipForwarding" | "ClubNotifications" | "RolePanelWebhooks" | "Fishing" | "FishingCustomization" | "FishingWebhooks" | "StreamRoles" | "RoleFilters" | "TwitterNotifications" | "TwitterLiveNotifications" | "BadgeAllGames" | "AutoRoles" | "AutoRoleTimers" | "AutoRoleInvites" | "StickyMessageRoleFilters" | "GiveawayClaims" | "GiveawayWinnerRole" | "InfluencerPanel" | "InfluencerPanelPremium" | "InfluencerPanelPlus" | "StatusRoleKeepOnOffline" | "YouTubeReplayChannel", string>;
251
+ t: Record<"BaseSyncableRoles" | "FullSyncableRoles" | "LiveRole" | "StatCounters" | "VoteSkipping" | "KickAuditLogs" | "LeaderboardCommand" | "RefreshingLeaderboard" | "LeaderBoardSyncRoles" | "BetaAccess" | "PriorityNotifications" | "KickClips" | "KickNotifications" | "TwitchNotifications" | "ImportEmojis" | "ImportEmojisWithSub" | "CustomWebhooks" | "AnyMessageEditor" | "SyncSocialUsernames" | "AutoDeleteNotifications" | "TestNotificationCommand" | "CustomCooldowns" | "WelcomeAndLeaveNotifications" | "NotificationPingRoleId" | "SwitchStreamerAndGuildEmbeds" | "BirthdayNotifications" | "AllowNotSendingOfflineNotifications" | "AutoStatusLiveRoles" | "RumbleNotifications" | "AllowAutoPublishNotifications" | "NotificationOverrides" | "UseCustomButtons" | "AllowVodFeatures" | "TikTokNotifications" | "StatusRoles" | "ManageBranding" | "StickyMessages" | "Starboards" | "CustomStarboards" | "AutoThreadOnStarboard" | "StarboardRewardRole" | "Giveaways" | "GiveawayLogs" | "ModifyGiveawayWhileRunning" | "ScheduleGiveaways" | "GiveawayRequirements" | "GiveawayBonusEntries" | "GiveawayEntryLimit" | "DisableDoubleVoteEntry" | "YouTubeNotifications" | "LinkedRoles" | "RandomMessages" | "WeeklyBirthdayPanel" | "GameNotifications" | "GameCategoryRouting" | "GameAllTimeLowAlerts" | "GameChangelogs" | "GameRobloxTracking" | "FixUrlWebhook" | "FixUrlCustomRules" | "KickClipperSubscribe" | "WhatnotNotifications" | "ScheduledEventMessages" | "ScheduledMessageWebhooks" | "ReactionRoles" | "AdvancedRolePanels" | "KickClipForwarding" | "TwitchClipForwarding" | "ClubNotifications" | "RolePanelWebhooks" | "Fishing" | "FishingCustomization" | "FishingWebhooks" | "StreamRoles" | "RoleFilters" | "TwitterNotifications" | "TwitterLiveNotifications" | "BadgeAllGames" | "AutoRoles" | "AutoRoleTimers" | "AutoRoleInvites" | "StickyMessageRoleFilters" | "GiveawayClaims" | "GiveawayWinnerRole" | "InfluencerPanel" | "InfluencerPanelPremium" | "InfluencerPanelPlus" | "StatusRoleKeepOnOffline" | "YouTubeReplayChannel" | "LivePostUpdates" | "MultiDestination", string>;
248
252
  all: bigint;
249
253
  static b: {
250
254
  BaseSyncableRoles: bigint;
@@ -336,8 +340,10 @@ export declare class GuildFeaturesManager extends BitFieldManager<typeof GuildFe
336
340
  InfluencerPanelPlus: bigint;
337
341
  StatusRoleKeepOnOffline: bigint;
338
342
  YouTubeReplayChannel: bigint;
343
+ LivePostUpdates: bigint;
344
+ MultiDestination: bigint;
339
345
  };
340
- static t: Record<"StickyMessages" | "LinkedRoles" | "StatusRoles" | "Starboards" | "KickClips" | "StatCounters" | "BaseSyncableRoles" | "FullSyncableRoles" | "LiveRole" | "VoteSkipping" | "KickAuditLogs" | "LeaderboardCommand" | "RefreshingLeaderboard" | "LeaderBoardSyncRoles" | "BetaAccess" | "PriorityNotifications" | "KickNotifications" | "TwitchNotifications" | "ImportEmojis" | "ImportEmojisWithSub" | "CustomWebhooks" | "AnyMessageEditor" | "SyncSocialUsernames" | "AutoDeleteNotifications" | "TestNotificationCommand" | "CustomCooldowns" | "WelcomeAndLeaveNotifications" | "NotificationPingRoleId" | "SwitchStreamerAndGuildEmbeds" | "BirthdayNotifications" | "AllowNotSendingOfflineNotifications" | "AutoStatusLiveRoles" | "RumbleNotifications" | "AllowAutoPublishNotifications" | "NotificationOverrides" | "UseCustomButtons" | "AllowVodFeatures" | "TikTokNotifications" | "ManageBranding" | "CustomStarboards" | "AutoThreadOnStarboard" | "StarboardRewardRole" | "Giveaways" | "GiveawayLogs" | "ModifyGiveawayWhileRunning" | "ScheduleGiveaways" | "GiveawayRequirements" | "GiveawayBonusEntries" | "GiveawayEntryLimit" | "DisableDoubleVoteEntry" | "YouTubeNotifications" | "RandomMessages" | "WeeklyBirthdayPanel" | "GameNotifications" | "GameCategoryRouting" | "GameAllTimeLowAlerts" | "GameChangelogs" | "GameRobloxTracking" | "FixUrlWebhook" | "FixUrlCustomRules" | "KickClipperSubscribe" | "WhatnotNotifications" | "ScheduledEventMessages" | "ScheduledMessageWebhooks" | "ReactionRoles" | "AdvancedRolePanels" | "KickClipForwarding" | "TwitchClipForwarding" | "ClubNotifications" | "RolePanelWebhooks" | "Fishing" | "FishingCustomization" | "FishingWebhooks" | "StreamRoles" | "RoleFilters" | "TwitterNotifications" | "TwitterLiveNotifications" | "BadgeAllGames" | "AutoRoles" | "AutoRoleTimers" | "AutoRoleInvites" | "StickyMessageRoleFilters" | "GiveawayClaims" | "GiveawayWinnerRole" | "InfluencerPanel" | "InfluencerPanelPremium" | "InfluencerPanelPlus" | "StatusRoleKeepOnOffline" | "YouTubeReplayChannel", string>;
346
+ static t: Record<"BaseSyncableRoles" | "FullSyncableRoles" | "LiveRole" | "StatCounters" | "VoteSkipping" | "KickAuditLogs" | "LeaderboardCommand" | "RefreshingLeaderboard" | "LeaderBoardSyncRoles" | "BetaAccess" | "PriorityNotifications" | "KickClips" | "KickNotifications" | "TwitchNotifications" | "ImportEmojis" | "ImportEmojisWithSub" | "CustomWebhooks" | "AnyMessageEditor" | "SyncSocialUsernames" | "AutoDeleteNotifications" | "TestNotificationCommand" | "CustomCooldowns" | "WelcomeAndLeaveNotifications" | "NotificationPingRoleId" | "SwitchStreamerAndGuildEmbeds" | "BirthdayNotifications" | "AllowNotSendingOfflineNotifications" | "AutoStatusLiveRoles" | "RumbleNotifications" | "AllowAutoPublishNotifications" | "NotificationOverrides" | "UseCustomButtons" | "AllowVodFeatures" | "TikTokNotifications" | "StatusRoles" | "ManageBranding" | "StickyMessages" | "Starboards" | "CustomStarboards" | "AutoThreadOnStarboard" | "StarboardRewardRole" | "Giveaways" | "GiveawayLogs" | "ModifyGiveawayWhileRunning" | "ScheduleGiveaways" | "GiveawayRequirements" | "GiveawayBonusEntries" | "GiveawayEntryLimit" | "DisableDoubleVoteEntry" | "YouTubeNotifications" | "LinkedRoles" | "RandomMessages" | "WeeklyBirthdayPanel" | "GameNotifications" | "GameCategoryRouting" | "GameAllTimeLowAlerts" | "GameChangelogs" | "GameRobloxTracking" | "FixUrlWebhook" | "FixUrlCustomRules" | "KickClipperSubscribe" | "WhatnotNotifications" | "ScheduledEventMessages" | "ScheduledMessageWebhooks" | "ReactionRoles" | "AdvancedRolePanels" | "KickClipForwarding" | "TwitchClipForwarding" | "ClubNotifications" | "RolePanelWebhooks" | "Fishing" | "FishingCustomization" | "FishingWebhooks" | "StreamRoles" | "RoleFilters" | "TwitterNotifications" | "TwitterLiveNotifications" | "BadgeAllGames" | "AutoRoles" | "AutoRoleTimers" | "AutoRoleInvites" | "StickyMessageRoleFilters" | "GiveawayClaims" | "GiveawayWinnerRole" | "InfluencerPanel" | "InfluencerPanelPremium" | "InfluencerPanelPlus" | "StatusRoleKeepOnOffline" | "YouTubeReplayChannel" | "LivePostUpdates" | "MultiDestination", string>;
341
347
  static all: bigint;
342
348
  convertBitToField<T extends EnumValues<typeof GuildFeatures>>(bit: T): string | null;
343
349
  }
@@ -233,6 +233,10 @@ exports.GuildFeatures = {
233
233
  // YouTube replays in their own channel. Replays are free and post in the upload channel; a dedicated
234
234
  // replay channel is Premium Plus.
235
235
  YouTubeReplayChannel: 1n << 89n,
236
+ // Live post updates: edit the live post when the streamer changes their title or category. Premium Plus.
237
+ LivePostUpdates: 1n << 90n,
238
+ // Multiple notification channels: up to 2 extra channels per creator, each with its own ping role. Premium Plus.
239
+ MultiDestination: 1n << 91n,
236
240
  };
237
241
  exports.GuildFeaturesText = {
238
242
  BaseSyncableRoles: 'Basic Syncable Roles',
@@ -326,6 +330,8 @@ exports.GuildFeaturesText = {
326
330
  InfluencerPanelPlus: 'Influencer Panel: Premium Plus',
327
331
  StatusRoleKeepOnOffline: 'Status Roles: Remove When Offline',
328
332
  YouTubeReplayChannel: 'YouTube: Replay Channel',
333
+ LivePostUpdates: 'Live Post Updates',
334
+ MultiDestination: 'Multiple Notification Channels',
329
335
  };
330
336
  exports.UserFeatures = {
331
337
  HasOneServerPremium: 1n << 0n,
@@ -1,4 +1,4 @@
1
- export declare const FEATURE_NAMES: readonly ["freeGames", "gameChangelogs", "robloxNotifications", "fixLinks", "clipperNotifications", "scheduledMessages", "linkedRoles", "reactionRoles", "clipForwarding", "club", "whatnot", "fishing", "giveaways", "starboards", "syncedRoles", "birthdays", "statusRoles", "autoStatusLiveRoles", "streamRoles", "badgeNotifications", "kickBounties", "autoRoles", "giveawayClaims", "influencerPanel", "publicGiveaways", "publicDrops", "sitePolls", "siteAnnouncements", "offlineGracePeriod", "youtubeReplays", "twitter", "voteToTest", "voteToAddSocial", "aiAssistant", "voteToUseAssistant", "assistantShareChat", "emotes", "liveRoles", "stickyMessages", "welcomeLeave", "statCounters", "voteSkipping"];
1
+ export declare const FEATURE_NAMES: readonly ["freeGames", "gameChangelogs", "robloxNotifications", "fixLinks", "clipperNotifications", "scheduledMessages", "linkedRoles", "reactionRoles", "clipForwarding", "club", "whatnot", "fishing", "giveaways", "starboards", "syncedRoles", "birthdays", "statusRoles", "autoStatusLiveRoles", "streamRoles", "badgeNotifications", "kickBounties", "autoRoles", "giveawayClaims", "influencerPanel", "publicGiveaways", "publicDrops", "sitePolls", "siteAnnouncements", "offlineGracePeriod", "youtubeReplays", "livePostUpdates", "multiDestination", "twitter", "voteToTest", "voteToAddSocial", "aiAssistant", "voteToUseAssistant", "assistantShareChat", "emotes", "liveRoles", "stickyMessages", "welcomeLeave", "statCounters", "voteSkipping"];
2
2
  export type FeatureName = (typeof FEATURE_NAMES)[number];
3
3
  export type FeatureStatus = 'off' | 'beta' | 'public';
4
4
  export type ResolvedFeature = 'public' | 'beta' | 'hidden';
@@ -35,6 +35,8 @@ exports.FEATURE_NAMES = [
35
35
  'siteAnnouncements',
36
36
  'offlineGracePeriod',
37
37
  'youtubeReplays',
38
+ 'livePostUpdates',
39
+ 'multiDestination',
38
40
  'twitter',
39
41
  'voteToTest',
40
42
  'voteToAddSocial',
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Multi-destination notifications - up to two extra channels per creator, each with its own ping role.
3
+ * Premium Plus (`MultiDestination`), launch flag `multiDestination`. Plan: phases/multi-destination/.
4
+ *
5
+ * Mirrors `src/modules/multiDestination.ts` in Noti-v3. Hand-copied; the server's `sdkDrift` test diffs everything
6
+ * below this header against it.
7
+ *
8
+ * One source of truth for the rules, because they have to agree across the zod schemas, the save
9
+ * transforms, the send path and the SDK. Deliberately has NO imports, for the same reason as
10
+ * `offlineGrace.ts`: the zod schemas pull from here, and a cycle through them is someone's afternoon.
11
+ *
12
+ * The creator's own channel + `pingRoleId` is destination 1 and is not stored here. `extraDestinations`
13
+ * holds only the extras, so a server that never opts in has nothing new anywhere.
14
+ */
15
+ /** Extra destinations a creator may have, on top of their own channel. 3 channels total. */
16
+ export declare const MAX_EXTRA_DESTINATIONS = 2;
17
+ /**
18
+ * Which post types an extra destination can be limited to, per platform. Empty `types` = everything.
19
+ *
20
+ * Offline is NOT a type: the offline path only ever edits or deletes the live post it announced, so a
21
+ * copy's offline follows its own live copy. Kick clips/VODs and the X post cooldown are separate
22
+ * features with their own channels, not notification types, so they are not here either.
23
+ */
24
+ export declare const DESTINATION_TYPES: {
25
+ readonly kick: readonly ["live"];
26
+ readonly twitch: readonly ["live"];
27
+ readonly rumble: readonly ["live", "video"];
28
+ readonly tiktok: readonly ["live", "video"];
29
+ readonly youtube: readonly ["live", "video", "replay"];
30
+ readonly twitter: readonly ["live", "post"];
31
+ readonly club: readonly ["post"];
32
+ };
33
+ export type DestinationPlatform = keyof typeof DESTINATION_TYPES;
34
+ export type DestinationType = (typeof DESTINATION_TYPES)[DestinationPlatform][number];
35
+ export type ExtraDestination = {
36
+ channelId: string;
37
+ /** A role id, `everyone`, `here`, or null for no ping - the same values the creator's own `pingRoleId` takes. */
38
+ pingRoleId: string | null;
39
+ types: DestinationType[];
40
+ };
41
+ /**
42
+ * A copy of the live post sent to an extra destination, so the offline can edit or delete it. `pingRoleId` is
43
+ * the role the copy was posted with: the offline edit rewrites the content, and it must show the copy's own
44
+ * role, not destination 1's - and not whatever the settings say by the time the stream ends.
45
+ */
46
+ export type ExtraPost = {
47
+ channelId: string;
48
+ messageId: string;
49
+ pingRoleId: string | null;
50
+ };
51
+ /**
52
+ * The stored form of whatever the dashboard sent. The save transforms call this, not only zod.
53
+ *
54
+ * Zod alone is not enough here: the creator routes validate with the PARTIAL schema, and `deepPartialify`
55
+ * keeps an array's `max` but drops custom refinements (the no-duplicates check) and makes every field
56
+ * of an item optional. So a row with no channel, a channel listed twice, or a type the platform does not
57
+ * have could all reach the database. This drops them instead of failing the whole save:
58
+ *
59
+ * - no valid `channelId` -> dropped
60
+ * - a channel already listed -> the later row dropped
61
+ * - a `pingRoleId` that is not a role / everyone / here -> no ping (null)
62
+ * - a type this platform does not have, or a repeat -> dropped from `types`
63
+ * - beyond `MAX_EXTRA_DESTINATIONS` -> cut
64
+ *
65
+ * `null` and anything that is not a list mean "no extras" and store `[]`: a Json column cannot take a
66
+ * plain null through Prisma, same as `Guild.statChannelFormats` storing `{}`.
67
+ */
68
+ export declare function normalizeExtraDestinations(platform: DestinationPlatform, input: unknown): ExtraDestination[];
69
+ /**
70
+ * The extras one notification goes to: the stored list, re-normalized (a row written before a rule changed
71
+ * still obeys today's rule), keeping only destinations that want this post type and whose channel is not
72
+ * already receiving it.
73
+ *
74
+ * `taken` is destination 1's channel AFTER any override redirect. An extra pointing at the same channel would
75
+ * post the notification there twice, so it is dropped here rather than rejected on save - a partial save body
76
+ * usually does not carry the creator's own channel, so the save could not check it.
77
+ */
78
+ export declare function extrasFor(platform: DestinationPlatform, stored: unknown, type: DestinationType, taken: (string | null | undefined)[]): ExtraDestination[];
79
+ /**
80
+ * The stored copies of a live post (`*CurrentlyLive.extraPosts`), read safely. Written only by the bot, but it is
81
+ * a Json column: anything malformed is dropped rather than trusted, and a pre-`pingRoleId` row reads as no role.
82
+ */
83
+ export declare function readExtraPosts(stored: unknown): ExtraPost[];
84
+ /** "Is there a live post to finish?" - destination 1's message, or any copy. Offline and sweep filters use this. */
85
+ export declare const hasLivePost: (live: {
86
+ channelId?: string | null;
87
+ messageId?: string | null;
88
+ extraPosts?: unknown;
89
+ } | null | undefined) => boolean;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Multi-destination notifications - up to two extra channels per creator, each with its own ping role.
4
+ * Premium Plus (`MultiDestination`), launch flag `multiDestination`. Plan: phases/multi-destination/.
5
+ *
6
+ * Mirrors `src/modules/multiDestination.ts` in Noti-v3. Hand-copied; the server's `sdkDrift` test diffs everything
7
+ * below this header against it.
8
+ *
9
+ * One source of truth for the rules, because they have to agree across the zod schemas, the save
10
+ * transforms, the send path and the SDK. Deliberately has NO imports, for the same reason as
11
+ * `offlineGrace.ts`: the zod schemas pull from here, and a cycle through them is someone's afternoon.
12
+ *
13
+ * The creator's own channel + `pingRoleId` is destination 1 and is not stored here. `extraDestinations`
14
+ * holds only the extras, so a server that never opts in has nothing new anywhere.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.hasLivePost = exports.DESTINATION_TYPES = exports.MAX_EXTRA_DESTINATIONS = void 0;
18
+ exports.normalizeExtraDestinations = normalizeExtraDestinations;
19
+ exports.extrasFor = extrasFor;
20
+ exports.readExtraPosts = readExtraPosts;
21
+ /** Extra destinations a creator may have, on top of their own channel. 3 channels total. */
22
+ exports.MAX_EXTRA_DESTINATIONS = 2;
23
+ /**
24
+ * Which post types an extra destination can be limited to, per platform. Empty `types` = everything.
25
+ *
26
+ * Offline is NOT a type: the offline path only ever edits or deletes the live post it announced, so a
27
+ * copy's offline follows its own live copy. Kick clips/VODs and the X post cooldown are separate
28
+ * features with their own channels, not notification types, so they are not here either.
29
+ */
30
+ exports.DESTINATION_TYPES = {
31
+ kick: ['live'],
32
+ twitch: ['live'],
33
+ rumble: ['live', 'video'],
34
+ tiktok: ['live', 'video'],
35
+ youtube: ['live', 'video', 'replay'],
36
+ twitter: ['live', 'post'],
37
+ club: ['post'],
38
+ };
39
+ const SNOWFLAKE = /^\d{17,21}$/;
40
+ const isPingTarget = (v) => typeof v === 'string' && (SNOWFLAKE.test(v) || v === 'everyone' || v === 'here');
41
+ /**
42
+ * The stored form of whatever the dashboard sent. The save transforms call this, not only zod.
43
+ *
44
+ * Zod alone is not enough here: the creator routes validate with the PARTIAL schema, and `deepPartialify`
45
+ * keeps an array's `max` but drops custom refinements (the no-duplicates check) and makes every field
46
+ * of an item optional. So a row with no channel, a channel listed twice, or a type the platform does not
47
+ * have could all reach the database. This drops them instead of failing the whole save:
48
+ *
49
+ * - no valid `channelId` -> dropped
50
+ * - a channel already listed -> the later row dropped
51
+ * - a `pingRoleId` that is not a role / everyone / here -> no ping (null)
52
+ * - a type this platform does not have, or a repeat -> dropped from `types`
53
+ * - beyond `MAX_EXTRA_DESTINATIONS` -> cut
54
+ *
55
+ * `null` and anything that is not a list mean "no extras" and store `[]`: a Json column cannot take a
56
+ * plain null through Prisma, same as `Guild.statChannelFormats` storing `{}`.
57
+ */
58
+ function normalizeExtraDestinations(platform, input) {
59
+ if (!Array.isArray(input))
60
+ return [];
61
+ const allowed = exports.DESTINATION_TYPES[platform];
62
+ const seen = new Set();
63
+ const out = [];
64
+ for (const raw of input) {
65
+ if (out.length >= exports.MAX_EXTRA_DESTINATIONS)
66
+ break;
67
+ if (!raw || typeof raw !== 'object')
68
+ continue;
69
+ const row = raw;
70
+ if (typeof row.channelId !== 'string' || !SNOWFLAKE.test(row.channelId) || seen.has(row.channelId))
71
+ continue;
72
+ seen.add(row.channelId);
73
+ const types = Array.isArray(row.types)
74
+ ? [...new Set(row.types.filter((t) => typeof t === 'string' && allowed.includes(t)))]
75
+ : [];
76
+ out.push({ channelId: row.channelId, pingRoleId: isPingTarget(row.pingRoleId) ? row.pingRoleId : null, types });
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * The extras one notification goes to: the stored list, re-normalized (a row written before a rule changed
82
+ * still obeys today's rule), keeping only destinations that want this post type and whose channel is not
83
+ * already receiving it.
84
+ *
85
+ * `taken` is destination 1's channel AFTER any override redirect. An extra pointing at the same channel would
86
+ * post the notification there twice, so it is dropped here rather than rejected on save - a partial save body
87
+ * usually does not carry the creator's own channel, so the save could not check it.
88
+ */
89
+ function extrasFor(platform, stored, type, taken) {
90
+ const used = new Set(taken.filter((id) => !!id));
91
+ return normalizeExtraDestinations(platform, stored).filter((d) => (!d.types.length || d.types.includes(type)) && !used.has(d.channelId));
92
+ }
93
+ /**
94
+ * The stored copies of a live post (`*CurrentlyLive.extraPosts`), read safely. Written only by the bot, but it is
95
+ * a Json column: anything malformed is dropped rather than trusted, and a pre-`pingRoleId` row reads as no role.
96
+ */
97
+ function readExtraPosts(stored) {
98
+ if (!Array.isArray(stored))
99
+ return [];
100
+ return stored.flatMap((raw) => {
101
+ const row = raw;
102
+ if (!row || typeof row.channelId !== 'string' || !SNOWFLAKE.test(row.channelId) || typeof row.messageId !== 'string' || !SNOWFLAKE.test(row.messageId))
103
+ return [];
104
+ return [{ channelId: row.channelId, messageId: row.messageId, pingRoleId: isPingTarget(row.pingRoleId) ? row.pingRoleId : null }];
105
+ });
106
+ }
107
+ /** "Is there a live post to finish?" - destination 1's message, or any copy. Offline and sweep filters use this. */
108
+ const hasLivePost = (live) => !!(live?.channelId && live?.messageId) || readExtraPosts(live?.extraPosts).length > 0;
109
+ exports.hasLivePost = hasLivePost;