@notidotbot/noti-api-client 1.4.23 → 1.5.0

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.
@@ -84,6 +84,55 @@ export interface ChangelogEntry {
84
84
  publishedAt: string;
85
85
  feedLabel: string;
86
86
  }
87
+ export type RobloxSignal = 'update' | 'event';
88
+ export interface RobloxEntry {
89
+ id: string;
90
+ signal: RobloxSignal;
91
+ universeId: number;
92
+ /** canonical root place — the game URL is roblox.com/games/<placeId> */
93
+ placeId: number;
94
+ gameTitle: string;
95
+ /** update: "<name> was updated" · event: the event title */
96
+ title: string;
97
+ /** event description; null for updates (no notes text exists) */
98
+ description: string | null;
99
+ url: string;
100
+ thumbnail: string | null;
101
+ /** event window (null for updates) */
102
+ startsAt: string | null;
103
+ endsAt: string | null;
104
+ /** event status: 'active' | 'upcoming' | … (null for updates) */
105
+ status: string | null;
106
+ /** live players (update signal); null for events */
107
+ playing: number | null;
108
+ publishedAt: string;
109
+ }
110
+ /** A followable Roblox discovery card (popular grid + name search). `following` reflects THIS
111
+ * guild's follows — the bot recomputes gameApi's GLOBAL flag against the guild before returning. */
112
+ export interface RobloxPopularGame {
113
+ universeId: number;
114
+ placeId: number;
115
+ name: string;
116
+ playing: number | null;
117
+ thumbnail: string | null;
118
+ following: boolean;
119
+ }
120
+ /** Live preview of a pasted `roblox.com/games/<id>` URL or bare place id before following.
121
+ * `inWatchlist` is recomputed against THIS guild's follows (not the global gameApi set). */
122
+ export interface RobloxLookupResult {
123
+ placeId: number;
124
+ universeId: number;
125
+ rootPlaceId: number | null;
126
+ title: string | null;
127
+ playing: number | null;
128
+ thumbnail: string | null;
129
+ inWatchlist: boolean;
130
+ }
131
+ /** Followable-card list — shared by the popular grid and the name search box. */
132
+ export interface RobloxDiscoverResponse {
133
+ items: RobloxPopularGame[];
134
+ total: number;
135
+ }
87
136
  export interface DealsCatalogResponse {
88
137
  items: DealItem[];
89
138
  nextCursor: string | null;
@@ -188,6 +237,20 @@ export interface ChangelogGame {
188
237
  channelId?: string | null;
189
238
  pingRoleId?: string | null;
190
239
  }
240
+ export type RobloxKind = 'updates' | 'events' | 'both';
241
+ /** A followed Roblox experience. Keyed by canonical root `placeId` (paste a URL or id); ids are
242
+ * strings because Roblox ids overflow a 32-bit number column. `universeId` is resolved by the gameApi. */
243
+ export interface RobloxGame {
244
+ dbId?: string;
245
+ placeId: string;
246
+ universeId?: string | null;
247
+ title?: string | null;
248
+ /** Which signal(s) to notify on. Default `both`. */
249
+ kinds?: RobloxKind;
250
+ /** Optional per-game routing override (else uses the guild's default Roblox channel/role). */
251
+ channelId?: string | null;
252
+ pingRoleId?: string | null;
253
+ }
191
254
  export interface GameConfig {
192
255
  guildId?: string;
193
256
  enabled: boolean;
@@ -217,9 +280,12 @@ export interface GameConfig {
217
280
  allTimeLowMaxPriceCents: number | null;
218
281
  changelogChannelId: string | null;
219
282
  changelogPingRoleId: string | null;
283
+ robloxChannelId: string | null;
284
+ robloxPingRoleId: string | null;
220
285
  categoryRoutes: GameCategoryRoute[];
221
286
  trackedGames: TrackedGame[];
222
287
  changelogGames: ChangelogGame[];
288
+ robloxGames: RobloxGame[];
223
289
  }
224
290
  /** Resolved premium entitlements + limits for the guild (gating is visual, never hidden). */
225
291
  export interface GameEntitlements {
@@ -228,6 +294,7 @@ export interface GameEntitlements {
228
294
  allTimeLow: boolean;
229
295
  customWebhooks: boolean;
230
296
  changelogs: boolean;
297
+ roblox: boolean;
231
298
  trackedGameLimit: number;
232
299
  }
233
300
  export interface GameConfigResponse {
@@ -274,8 +341,14 @@ export type GamesWsEvent = {
274
341
  type: 'changelog.new';
275
342
  at: string;
276
343
  changelog: ChangelogEntry;
344
+ } | {
345
+ op: 'event';
346
+ type: GameRobloxEventType;
347
+ at: string;
348
+ roblox: RobloxEntry;
277
349
  };
278
350
  export type GameDealEventType = 'deal.new' | 'deal.updated' | 'deal.expired' | 'deal.enriched' | 'free.new' | 'free.upcoming' | 'free.expired' | 'free.weekend' | 'deal.lowest';
351
+ export type GameRobloxEventType = 'roblox.update' | 'roblox.event';
279
352
  export declare class APIGames {
280
353
  private web;
281
354
  constructor(web: WebDataManager);
@@ -287,6 +360,11 @@ export declare class APIGames {
287
360
  updateChangelogGames({ auth, guildId, games }: GamesFunctionsInput['updateChangelogGames']): Promise<WebResponse<{
288
361
  games: ChangelogGame[];
289
362
  }>>;
363
+ /** Replace the guild's followed Roblox experiences (replace-semantics). `placeId` accepts a game
364
+ * URL, a bare id, or a numeric string — the bot extracts the place id. Capped at 500. */
365
+ updateRobloxGames({ auth, guildId, games }: GamesFunctionsInput['updateRobloxGames']): Promise<WebResponse<{
366
+ games: RobloxGame[];
367
+ }>>;
290
368
  updateCategoryRoutes({ auth, guildId, routes }: GamesFunctionsInput['updateCategoryRoutes']): Promise<WebResponse<{
291
369
  routes: GameCategoryRoute[];
292
370
  }>>;
@@ -301,6 +379,13 @@ export declare class APIGames {
301
379
  getHealth({ auth, guildId }: GamesFunctionsInput['getHealth']): Promise<WebResponse<GameHealthBundle>>;
302
380
  /** Full Steam catalog search by name (proxied from the gameApi) — find ANY Steam game, not just catalog deals. */
303
381
  searchSteam({ auth, guildId, q, limit }: GamesFunctionsInput['searchSteam']): Promise<WebResponse<SteamSearchResponse>>;
382
+ /** Discover grid — top ~100 Roblox games by players. `following` reflects THIS guild's follows. */
383
+ getRobloxPopular({ auth, guildId }: GamesFunctionsInput['getRobloxPopular']): Promise<WebResponse<RobloxDiscoverResponse>>;
384
+ /** Search Roblox games by name (live proxy). Same followable-card shape as the popular grid. */
385
+ searchRoblox({ auth, guildId, q, limit }: GamesFunctionsInput['searchRoblox']): Promise<WebResponse<RobloxDiscoverResponse>>;
386
+ /** Preview a pasted `roblox.com/games/<id>` URL or bare place id before following — resolves the
387
+ * title/thumbnail/players. `placeId` accepts a URL or id; the bot extracts the numeric id. */
388
+ lookupRoblox({ auth, guildId, placeId }: GamesFunctionsInput['lookupRoblox']): Promise<WebResponse<RobloxLookupResult>>;
304
389
  }
305
390
  export type GameDealsQuery = {
306
391
  kind?: string;
@@ -331,7 +416,7 @@ export type GamesFunctionsInput = {
331
416
  updateConfig: {
332
417
  auth: string;
333
418
  guildId: string;
334
- } & Partial<Omit<GameConfig, 'guildId' | 'categoryRoutes' | 'trackedGames' | 'changelogGames'>>;
419
+ } & Partial<Omit<GameConfig, 'guildId' | 'categoryRoutes' | 'trackedGames' | 'changelogGames' | 'robloxGames'>>;
335
420
  updateTrackedGames: {
336
421
  auth: string;
337
422
  guildId: string;
@@ -342,6 +427,11 @@ export type GamesFunctionsInput = {
342
427
  guildId: string;
343
428
  games: ChangelogGame[];
344
429
  };
430
+ updateRobloxGames: {
431
+ auth: string;
432
+ guildId: string;
433
+ games: RobloxGame[];
434
+ };
345
435
  updateCategoryRoutes: {
346
436
  auth: string;
347
437
  guildId: string;
@@ -394,6 +484,21 @@ export type GamesFunctionsInput = {
394
484
  q: string;
395
485
  limit?: number;
396
486
  };
487
+ getRobloxPopular: {
488
+ auth: string;
489
+ guildId: string;
490
+ };
491
+ searchRoblox: {
492
+ auth: string;
493
+ guildId: string;
494
+ q: string;
495
+ limit?: number;
496
+ };
497
+ lookupRoblox: {
498
+ auth: string;
499
+ guildId: string;
500
+ placeId: string | number;
501
+ };
397
502
  };
398
503
  export type GamesReturnTypes = {
399
504
  getConfigRaw: Awaited<ReturnType<APIGames['getConfig']>>;
@@ -20,6 +20,11 @@ class APIGames {
20
20
  async updateChangelogGames({ auth, guildId, games }) {
21
21
  return this.web.request({ method: 'PUT', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/changelogs`), body: { games } });
22
22
  }
23
+ /** Replace the guild's followed Roblox experiences (replace-semantics). `placeId` accepts a game
24
+ * URL, a bare id, or a numeric string — the bot extracts the place id. Capped at 500. */
25
+ async updateRobloxGames({ auth, guildId, games }) {
26
+ return this.web.request({ method: 'PUT', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/roblox`), body: { games } });
27
+ }
23
28
  async updateCategoryRoutes({ auth, guildId, routes }) {
24
29
  return this.web.request({ method: 'PUT', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/routes`), body: { routes } });
25
30
  }
@@ -53,5 +58,19 @@ class APIGames {
53
58
  async searchSteam({ auth, guildId, q, limit }) {
54
59
  return this.web.request({ method: 'GET', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/steam/search`, { q, limit }) });
55
60
  }
61
+ // ── Roblox discovery (three ways to find a followable game) ───────────────
62
+ /** Discover grid — top ~100 Roblox games by players. `following` reflects THIS guild's follows. */
63
+ async getRobloxPopular({ auth, guildId }) {
64
+ return this.web.request({ method: 'GET', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/roblox/popular`) });
65
+ }
66
+ /** Search Roblox games by name (live proxy). Same followable-card shape as the popular grid. */
67
+ async searchRoblox({ auth, guildId, q, limit }) {
68
+ return this.web.request({ method: 'GET', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/roblox/search`, { q, limit }) });
69
+ }
70
+ /** Preview a pasted `roblox.com/games/<id>` URL or bare place id before following — resolves the
71
+ * title/thumbnail/players. `placeId` accepts a URL or id; the bot extracts the numeric id. */
72
+ async lookupRoblox({ auth, guildId, placeId }) {
73
+ return this.web.request({ method: 'GET', auth, endpoint: this.web.qp(`/data/guild/${guildId}/games/roblox/lookup`, { placeId }) });
74
+ }
56
75
  }
57
76
  exports.APIGames = APIGames;
@@ -67,6 +67,8 @@ export declare const GameConfigZod: {
67
67
  allTimeLowMaxPriceCents: z.ZodNullable<z.ZodNumber>;
68
68
  changelogChannelId: z.ZodNullable<z.ZodString>;
69
69
  changelogPingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
70
+ robloxChannelId: z.ZodNullable<z.ZodString>;
71
+ robloxPingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
70
72
  categoryRoutes: z.ZodArray<z.ZodLazy<z.ZodObject<{
71
73
  kinds: z.ZodArray<z.ZodEnum<{
72
74
  free_game: "free_game";
@@ -118,6 +120,18 @@ export declare const GameConfigZod: {
118
120
  channelId: z.ZodNullable<z.ZodString>;
119
121
  pingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
120
122
  }, z.core.$strip>>>;
123
+ robloxGames: z.ZodArray<z.ZodLazy<z.ZodObject<{
124
+ placeId: z.ZodString;
125
+ universeId: z.ZodNullable<z.ZodString>;
126
+ title: z.ZodNullable<z.ZodString>;
127
+ kinds: z.ZodEnum<{
128
+ updates: "updates";
129
+ events: "events";
130
+ both: "both";
131
+ }>;
132
+ channelId: z.ZodNullable<z.ZodString>;
133
+ pingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
134
+ }, z.core.$strip>>>;
121
135
  }, z.core.$strip>;
122
136
  readonly GuildGameCategoryRouteSchema: z.ZodObject<{
123
137
  kinds: z.ZodArray<z.ZodEnum<{
@@ -170,5 +184,17 @@ export declare const GameConfigZod: {
170
184
  channelId: z.ZodNullable<z.ZodString>;
171
185
  pingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
172
186
  }, z.core.$strip>;
187
+ readonly GuildRobloxGameSchema: z.ZodObject<{
188
+ placeId: z.ZodString;
189
+ universeId: z.ZodNullable<z.ZodString>;
190
+ title: z.ZodNullable<z.ZodString>;
191
+ kinds: z.ZodEnum<{
192
+ updates: "updates";
193
+ events: "events";
194
+ both: "both";
195
+ }>;
196
+ channelId: z.ZodNullable<z.ZodString>;
197
+ pingRoleId: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"everyone">, z.ZodLiteral<"here">]>>;
198
+ }, z.core.$strip>;
173
199
  };
174
200
  export declare const GameConfigZodPartial: PartialZodObject<typeof GameConfigZod>;
@@ -42,6 +42,16 @@ const GuildGameChangelogSchema = zod_2.z.object({
42
42
  channelId: zod_1.SnowFlake.nullable(),
43
43
  pingRoleId: zod_1.SnowFlakeOrEveryone.nullable(),
44
44
  });
45
+ // A Roblox experience the guild follows (updates and/or events). Keyed by canonical root placeId;
46
+ // ids are strings because Roblox ids overflow a 32-bit Int. `universeId` is resolved by the gameApi.
47
+ const GuildRobloxGameSchema = zod_2.z.object({
48
+ placeId: zod_2.z.string(),
49
+ universeId: zod_2.z.string().nullable(),
50
+ title: zod_2.z.string().max(200).nullable(),
51
+ kinds: schema_zod_1.GameRobloxKindEnum,
52
+ channelId: zod_1.SnowFlake.nullable(),
53
+ pingRoleId: zod_1.SnowFlakeOrEveryone.nullable(),
54
+ });
45
55
  const GuildGameConfigSchema = zod_2.z.object({
46
56
  guildId: zod_1.SnowFlake,
47
57
  enabled: zod_2.z.boolean(),
@@ -79,14 +89,19 @@ const GuildGameConfigSchema = zod_2.z.object({
79
89
  // Game changelogs routing (PatchBot-style).
80
90
  changelogChannelId: zod_1.SnowFlake.nullable(),
81
91
  changelogPingRoleId: zod_1.SnowFlakeOrEveryone.nullable(),
92
+ // Roblox experience tracking routing.
93
+ robloxChannelId: zod_1.SnowFlake.nullable(),
94
+ robloxPingRoleId: zod_1.SnowFlakeOrEveryone.nullable(),
82
95
  categoryRoutes: (0, zod_1.NoDuplicate)(zod_2.z.array(zod_2.z.lazy(() => GuildGameCategoryRouteSchema)), ['priority']),
83
96
  trackedGames: zod_2.z.array(zod_2.z.lazy(() => GuildTrackedGameSchema)),
84
97
  changelogGames: (0, zod_1.NoDuplicate)(zod_2.z.array(zod_2.z.lazy(() => GuildGameChangelogSchema)), ['steamAppId']),
98
+ robloxGames: (0, zod_1.NoDuplicate)(zod_2.z.array(zod_2.z.lazy(() => GuildRobloxGameSchema)), ['placeId']),
85
99
  });
86
100
  exports.GameConfigZod = {
87
101
  GuildGameConfigSchema,
88
102
  GuildGameCategoryRouteSchema,
89
103
  GuildTrackedGameSchema,
90
104
  GuildGameChangelogSchema,
105
+ GuildRobloxGameSchema,
91
106
  };
92
107
  exports.GameConfigZodPartial = Object.fromEntries(Object.entries(exports.GameConfigZod).map(([key, value]) => [key, (0, schema_zod_1.deepPartialify)(value)]));
@@ -160,3 +160,8 @@ export declare const GameDeliveryEnum: z.ZodEnum<{
160
160
  instant: "instant";
161
161
  daily: "daily";
162
162
  }>;
163
+ export declare const GameRobloxKindEnum: z.ZodEnum<{
164
+ updates: "updates";
165
+ events: "events";
166
+ both: "both";
167
+ }>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GameDeliveryEnum = exports.GameFlagEnum = exports.GameKindEnum = exports.GameStoreEnum = exports.UserNotificationSeverityEnum = exports.RoleBonusEnum = exports.MediaShareCommandsEnum = exports.MediaShareTypesEnum = exports.FilterEnum = exports.QueryInEnum = exports.RumbleTypeEnum = exports.PlatformsWithVideosEnum = exports.UserMediaSharePermissionsEnum = exports.ClipperCategoryEnum = exports.PlatformEnum = exports.OverridesEnum = exports.GuildSingleMessageTypeEnum = exports.GuildMessageTypeEnum = exports.StreamerMessageTypeEnum = exports.SingleMessageTypeEnum = exports.LeaderBoardTypesEnum = void 0;
3
+ exports.GameRobloxKindEnum = exports.GameDeliveryEnum = exports.GameFlagEnum = exports.GameKindEnum = exports.GameStoreEnum = exports.UserNotificationSeverityEnum = exports.RoleBonusEnum = exports.MediaShareCommandsEnum = exports.MediaShareTypesEnum = exports.FilterEnum = exports.QueryInEnum = exports.RumbleTypeEnum = exports.PlatformsWithVideosEnum = exports.UserMediaSharePermissionsEnum = exports.ClipperCategoryEnum = exports.PlatformEnum = exports.OverridesEnum = exports.GuildSingleMessageTypeEnum = exports.GuildMessageTypeEnum = exports.StreamerMessageTypeEnum = exports.SingleMessageTypeEnum = exports.LeaderBoardTypesEnum = void 0;
4
4
  exports.deepPartialify = deepPartialify;
5
5
  const zod_1 = require("zod");
6
6
  function deepPartialify(schema) {
@@ -214,3 +214,8 @@ exports.GameDeliveryEnum = zod_1.z.enum([
214
214
  'daily',
215
215
  'weekly',
216
216
  ]);
217
+ exports.GameRobloxKindEnum = zod_1.z.enum([
218
+ 'updates',
219
+ 'events',
220
+ 'both',
221
+ ]);