multichat-ts 0.0.93 → 0.0.94

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,676 +1,676 @@
1
- import Pusher from 'pusher-js';
2
-
3
- import {
4
- type BadgeURLsByNameOrCount,
5
- type BodyComponent,
6
- type EmoteURLsByName,
7
- type Message,
8
- } from './index.js';
9
-
10
- type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
11
- type ConnectionStateEvent = { previous: ConnectionState; current: ConnectionState };
12
-
13
- type EventCallbackFunctions = {
14
- message: (message: Message) => unknown;
15
- subscription: (data: ChatroomsV2Events['SubscriptionEvent']) => unknown;
16
- gifted: (data: ChatroomV1Events['GiftedSubscriptionsEvent']) => unknown;
17
- raw_message: (message: ChatMessageEvent) => unknown;
18
- connection_state_changed: (state: ConnectionStateEvent) => unknown;
19
- };
20
-
21
- type EventNames = keyof EventCallbackFunctions;
22
-
23
- const DEFAULT_KICK_PUSHER_KEY = '32cbd69e4b950bf97679';
24
-
25
- export class KickPusher {
26
- public kick_pusher_key = DEFAULT_KICK_PUSHER_KEY;
27
- public channel_name?: string;
28
- private assets: {
29
- external_emotes: EmoteURLsByName;
30
- badges: BadgeURLsByNameOrCount;
31
- } = {
32
- external_emotes: {},
33
- badges: {
34
- founder: '/svgs/badges/default-kick/founder.svg',
35
- moderator: '/svgs/badges/default-kick/moderator.svg',
36
- og: '/svgs/badges/default-kick/og.svg',
37
- sub_gifter: {
38
- 1: '/svgs/badges/default-kick/sub-gifter-blue.svg',
39
- 25: '/svgs/badges/default-kick/sub-gifter-purple.svg',
40
- 50: '/svgs/badges/default-kick/sub-gifter-red.svg',
41
- 100: '/svgs/badges/default-kick/sub-gifter-yellow.svg',
42
- 200: '/svgs/badges/default-kick/sub-gifter-green.svg',
43
- },
44
- verified: '/svgs/badges/default-kick/verified.svg',
45
- vip: '/svgs/badges/default-kick/vip.svg',
46
- broadcaster: '/svgs/badges/default-kick/broadcaster.svg',
47
- staff: '/svgs/badges/default-kick/staff.svg',
48
- },
49
- };
50
-
51
- private public_listeners: Partial<EventCallbackFunctions> = {};
52
-
53
- public socket?: Pusher;
54
- public isConnected = false;
55
-
56
- public setBadges(badges: BadgeURLsByNameOrCount) {
57
- this.assets.badges = { ...this.assets.badges, ...badges };
58
- }
59
-
60
- public setExternalEmotes(external_emotes: EmoteURLsByName) {
61
- this.assets.external_emotes = { ...this.assets.external_emotes, ...external_emotes };
62
- }
63
-
64
- public getStoredBadges() {
65
- return this.assets.badges;
66
- }
67
-
68
- public getStoredExternalEmotes() {
69
- return this.assets.external_emotes;
70
- }
71
-
72
- public async connect(
73
- channel?: { channelName?: string },
74
- get_channel: (channelName: string) => Promise<GetChannelResponse | undefined> = async (
75
- channelName,
76
- ) => {
77
- const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
78
- headers: {
79
- accept: 'aplication/json',
80
- 'user-agent':
81
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
82
- },
83
- });
84
- const json = await res.json();
85
- return json as GetChannelResponse | undefined;
86
- },
87
- ) {
88
- if (channel?.channelName) this.channel_name = channel.channelName;
89
- if (!this.channel_name) return console.error('channel_name not specified');
90
-
91
- console.log(`connecting to ${this.channel_name}...`);
92
-
93
- const channel_response = await get_channel(this.channel_name);
94
-
95
- if (!channel_response) return console.error('Failed to connect to Kick.com chat');
96
-
97
- channel_response.subscriber_badges.forEach((subscriber_badge) => {
98
- this.assets.badges['subscriber'] = {
99
- ...(this.assets.badges['subscriber'] ?? {}),
100
- [subscriber_badge.months]: subscriber_badge.badge_image.src,
101
- };
102
- });
103
-
104
- this.disconnect();
105
-
106
- this.socket = new Pusher(this.kick_pusher_key, {
107
- cluster: 'us2',
108
- });
109
- /*
110
- | 'App\\Events\\ChatMessageEvent'
111
- | 'App\\Events\\ChatroomClearEvent'
112
- | 'App\\Events\\ChatroomUpdatedEvent'
113
- | 'App\\Events\\GiftedSubscriptionsEvent'
114
- | 'App\\Events\\MessageDeletedEvent'
115
- | 'App\\Events\\PinnedMessageCreatedEvent'
116
- | 'App\\Events\\PinnedMessageDeletedEvent'
117
- | 'App\\Events\\PollDeleteEvent'
118
- | 'App\\Events\\PollUpdateEvent'
119
- | 'App\\Events\\StreamHostEvent'
120
- | 'App\\Events\\SubscriptionEvent'
121
- | 'App\\Events\\UserBannedEvent'
122
- | 'App\\Events\\UserUnbannedEvent'
123
- */
124
- this.socket.subscribe(`chatroom_${channel_response.chatroom.id}`);
125
- this.socket.subscribe(`chatrooms.${channel_response.chatroom.id}.v2`);
126
-
127
- this.socket
128
- .bind('pusher:subscription_succeeded', () =>
129
- this.onSubscriptionSuccess('pusher:subscription_succeeded'),
130
- )
131
- .bind('App\\Events\\ChatMessageEvent', (data: ChatroomsV2Events['ChatMessageEvent']) =>
132
- this.onChatMessage(data),
133
- )
134
- .bind('App\\Events\\SubscriptionEvent', (data: ChatroomsV2Events['SubscriptionEvent']) =>
135
- this.onChatSubscription(data),
136
- )
137
- .bind('GiftedSubscriptionsEvent', (data: ChatroomV1Events['GiftedSubscriptionsEvent']) =>
138
- this.onChatGifted(data),
139
- );
140
-
141
- this.socket.connection.bind('state_change', (state: ConnectionStateEvent) => {
142
- switch (state.current) {
143
- case 'connected': {
144
- this.isConnected = true;
145
- console.log(`Connected to Kick Pusher (${this.channel_name})!`);
146
- break;
147
- }
148
- case 'connecting': {
149
- this.isConnected = false;
150
- console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
151
- break;
152
- }
153
- case 'failed': {
154
- this.isConnected = false;
155
- console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
156
- break;
157
- }
158
- case 'unavailable': {
159
- this.isConnected = false;
160
- console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
161
- break;
162
- }
163
- default: {
164
- this.isConnected = false;
165
- break;
166
- }
167
- }
168
- this.public_listeners.connection_state_changed?.(state);
169
- });
170
- }
171
-
172
- public disconnect() {
173
- this.socket?.disconnect();
174
- this.socket?.unbind_all();
175
- }
176
-
177
- public on<EventName extends EventNames>(
178
- event_name: EventName,
179
- callback_fn: EventCallbackFunctions[EventName],
180
- ) {
181
- this.public_listeners[event_name] = callback_fn;
182
- }
183
-
184
- private onSubscriptionSuccess(channel: string) {
185
- console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
186
- }
187
-
188
- private onChatSubscription(data: ChatroomsV2Events['SubscriptionEvent']) {
189
- this.public_listeners.subscription?.(data);
190
- }
191
-
192
- private onChatGifted(data: ChatroomV1Events['GiftedSubscriptionsEvent']) {
193
- this.public_listeners.gifted?.(data);
194
- }
195
-
196
- private onChatMessage(data: ChatroomsV2Events['ChatMessageEvent']) {
197
- this.public_listeners.raw_message?.(data);
198
- const text = data.content;
199
- const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
200
-
201
- const body: BodyComponent[] = [];
202
-
203
- emote_matches.forEach((match) => {
204
- const emote_string = match[0];
205
- const emote_parts = emote_string.slice(1, emote_string.length - 1).split(':');
206
- const emote_id = emote_parts[1];
207
- if (!emote_id) return;
208
-
209
- body.push({
210
- type: 'emote',
211
- start_inclusive: match.index,
212
- end_exclusive: match.index + emote_string.length,
213
- url: `https://files.kick.com/emotes/${emote_id}/fullsize`,
214
- });
215
- });
216
-
217
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
218
-
219
- const old_body_length = body.length;
220
-
221
- if (old_body_length > 0) {
222
- body.forEach((segment, index) => {
223
- const previous_segment = body[index - 1];
224
-
225
- const text_start_inclusive =
226
- previous_segment?.end_exclusive !== undefined ? previous_segment.end_exclusive + 1 : 0;
227
- const text_end_exclusive = Math.max(0, segment.start_inclusive);
228
-
229
- if (text_end_exclusive - text_start_inclusive > 0) {
230
- body.push({
231
- type: 'text',
232
- text: text.slice(text_start_inclusive, text_end_exclusive),
233
- start_inclusive: text_start_inclusive,
234
- end_exclusive: text_end_exclusive,
235
- });
236
- }
237
- if (index === old_body_length - 1 && segment.end_exclusive < text.length - 1) {
238
- body.push({
239
- type: 'text',
240
- text: text.slice(segment.end_exclusive),
241
- start_inclusive: segment.end_exclusive,
242
- end_exclusive: text.length,
243
- });
244
- }
245
- });
246
- } else {
247
- body.push({
248
- type: 'text',
249
- text,
250
- start_inclusive: 0,
251
- end_exclusive: text.length,
252
- });
253
- }
254
-
255
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
256
- const message: Message = {
257
- id: data.id,
258
- user: {
259
- id: `${data.sender.id}`,
260
- username: data.sender.slug ?? data.sender.username.toLowerCase(),
261
- display_name: data.sender.username,
262
- roles: {},
263
- color: data.sender.identity.color,
264
- badges: data.sender.identity.badges.flatMap((badge) => {
265
- let badge_url: string | undefined = undefined;
266
-
267
- const badge_url_or_counts = this.assets.badges[badge.type];
268
- const badge_count = badge.count;
269
-
270
- if (typeof badge_url_or_counts === 'string') badge_url = badge_url_or_counts;
271
- else if (typeof badge_url_or_counts === 'object' && badge_count !== undefined) {
272
- const badge_entry_by_count = Object.entries(badge_url_or_counts)
273
- .sort(([a_min_count], [b_min_count]) => Number(a_min_count) - Number(b_min_count))
274
- .find(([min_count]) => badge_count >= Number(min_count));
275
- if (badge_entry_by_count) badge_url = badge_entry_by_count[1];
276
- }
277
-
278
- if (!badge_url) return [];
279
- return {
280
- set_id: badge.type,
281
- url: badge_url,
282
- info: String(badge.count),
283
- };
284
- }),
285
- },
286
- body,
287
- channel: {
288
- room_id: String(data.chatroom_id),
289
- name: this.channel_name ?? 'unknown',
290
- },
291
- raw_text: data.content,
292
- timestamp_sent: Date.parse(data.created_at),
293
- };
294
-
295
- if (data.type === 'celebration' && data.metadata?.celebration) {
296
- message.resubscription = {
297
- id: data.metadata.celebration.id,
298
- months: data.metadata.celebration.total_months,
299
- subscribed_since_timestamp: data.metadata.celebration.created_at,
300
- };
301
- }
302
- this.public_listeners.message?.(message);
303
- }
304
- }
305
-
306
- export interface GetChannelResponse {
307
- id: number;
308
- user_id: number;
309
- slug: string;
310
- is_banned: boolean;
311
- playback_url?: string;
312
- vod_enabled: boolean;
313
- subscription_enabled: boolean;
314
- followers_count: number;
315
- following?: boolean;
316
- subscription?: unknown;
317
- subscriber_badges: Array<{
318
- id: number;
319
- channel_id: number;
320
- months: number;
321
- badge_image: {
322
- srcset: string;
323
- src: string;
324
- };
325
- }>;
326
- banner_image?: {
327
- url: string;
328
- };
329
- livestream?: ChannelLivestream;
330
- role?: unknown;
331
- muted: boolean;
332
- follower_badges: unknown[];
333
- offline_banner_image: unknown;
334
- verified: boolean;
335
- recent_categories: Array<{
336
- id: number;
337
- category_id: number;
338
- name: string;
339
- slug: string;
340
- tags: string[];
341
- description?: string;
342
- deleted_at: unknown;
343
- viewers: number;
344
- banner: {
345
- responsive: string;
346
- url: string;
347
- };
348
- category: {
349
- id: number;
350
- name: string;
351
- slug: string;
352
- icon: string;
353
- };
354
- }>;
355
- can_host: boolean;
356
- user: {
357
- id: number;
358
- username: string;
359
- agreed_to_terms: true;
360
- email_verified_at: Date;
361
- bio?: string;
362
- country?: string;
363
- state?: string;
364
- city?: string;
365
- instagram?: string;
366
- twitter?: string;
367
- youtube?: string;
368
- discord?: string;
369
- tiktok?: string;
370
- facebook?: string;
371
- profile_pic?: string;
372
- };
373
- chatroom: ChannelChatroom;
374
- ascending_links?: Array<{
375
- id: number;
376
- channel_id: number;
377
- description: string;
378
- link: string;
379
- created_at: Date;
380
- updated_at: Date;
381
- order: number;
382
- title: string;
383
- }>;
384
- }
385
-
386
- export interface ChannelLivestream {
387
- id: number;
388
- slug: string;
389
- channel_id: number;
390
- created_at: Date;
391
- session_title: string;
392
- is_live: boolean;
393
- risk_level_id: unknown;
394
- start_time: Date;
395
- source: unknown;
396
- twitch_channel: unknown;
397
- duration: number;
398
- language: string;
399
- is_mature: boolean;
400
- viewer_count: number;
401
- thumbnail: {
402
- url: string;
403
- };
404
- categories: Array<{
405
- id: number;
406
- category_id: number;
407
- name: string;
408
- slug: string;
409
- tags: string[];
410
- description?: string;
411
- deleted_at: unknown;
412
- viewers: number;
413
- category: {
414
- id: number;
415
- name: string;
416
- slug: string;
417
- icon: string;
418
- };
419
- }>;
420
- tags: unknown[];
421
- }
422
- export interface ChannelChatroom {
423
- id: number;
424
- chatable_type: string;
425
- channel_id: string;
426
- created_at: Date;
427
- updated_at: Date;
428
- chat_mode_old: string;
429
- chat_mode: string;
430
- slow_mode: boolean;
431
- chatable_id: number;
432
- followers_mode: boolean;
433
- subscribers_mode: boolean;
434
- emotes_mode: boolean;
435
- message_interval: number;
436
- following_min_duration: number;
437
- }
438
- export interface ChatMessageEvent {
439
- id: string;
440
- chatroom_id: number;
441
- content: string;
442
- type: 'message' | 'celebration';
443
- created_at: string;
444
- metadata?: {
445
- celebration: {
446
- created_at: string;
447
- id: string;
448
- total_months: number;
449
- type: 'subscription_renewed';
450
- };
451
- };
452
- sender: {
453
- id: number;
454
- username: string;
455
- slug?: string;
456
- identity: {
457
- color: string;
458
- badges: Array<{
459
- type: string;
460
- text: string;
461
- count?: number;
462
- }>;
463
- };
464
- };
465
- }
466
-
467
- export interface ChannelEvents {
468
- // {"user_ids":[262477],"username":"Biceus","channel_id":145224}
469
- ChannelSubscriptionEvent: {
470
- user_ids: number[];
471
- username: string;
472
- channel_id: number;
473
- };
474
- // {"leaderboard":[{"user_id":22,"username":"Eddie","quantity":2366},{"user_id":4349824,"username":"TinyTwink","quantity":1878},{"user_id":278971,"username":"Riri_The_Reducer","quantity":1171},{"user_id":703016,"username":"mewtoe","quantity":1143},{"user_id":24981650,"username":"ThailandMonger","quantity":750},{"user_id":5281070,"username":"Annoyinmous","quantity":700},{"user_id":739621,"username":"kfcbelly","quantity":621},{"user_id":367058,"username":"xWendy","quantity":512},{"user_id":35722,"username":"ABZ","quantity":509},{"user_id":723,"username":"Trainwreckstv","quantity":500}],"weekly_leaderboard":[{"user_id":11837402,"username":"BigHornsBigKnives","quantity":5},{"user_id":336982,"username":"krook","quantity":4},{"user_id":1579867,"username":"deadmoney6","quantity":1}],"monthly_leaderboard":[{"user_id":33225,"username":"MRQUICK666","quantity":10},{"user_id":8696811,"username":"Nate09","quantity":10},{"user_id":8561504,"username":"ridorana12","quantity":7},{"user_id":11837402,"username":"BigHornsBigKnives","quantity":5},{"user_id":4239451,"username":"Slippy151","quantity":5},{"user_id":48392334,"username":"isokeith","quantity":5},{"user_id":4341321,"username":"Stonkzz","quantity":5},{"user_id":16894707,"username":"Turtle427","quantity":5},{"user_id":336982,"username":"krook","quantity":4},{"user_id":76184,"username":"complicated","quantity":2}],"gifter_id":11837402,"gifter_username":"BigHornsBigKnives","gifted_quantity":5}
475
- GiftsLeaderboardUpdated: {
476
- leaderboard: {
477
- user_id: number;
478
- username: string;
479
- quantity: number;
480
- }[];
481
- weekly_leaderboard: {
482
- user_id: number;
483
- username: string;
484
- quantity: number;
485
- }[];
486
- monthly_leaderboard: {
487
- user_id: number;
488
- username: string;
489
- quantity: number;
490
- }[];
491
- gifter_id: number;
492
- gifter_username: string;
493
- gifted_quantity: number;
494
- };
495
-
496
- // {"channel":{"id":145224,"user_id":146923,"slug":"iceposeidon","is_banned":false,"playback_url":"https://fa723fc1b171.us-west-2.playback.live-video.net/api/video/v1/us-west-2.196233775518.channel.aAMMC00nhtv0.m3u8","name_updated_at":null,"vod_enabled":true,"subscription_enabled":true,"is_affiliate":false,"can_host":true,"chatroom":{"id":145222,"chatable_type":"App\\Models\\Channel","channel_id":145224,"created_at":"2022-12-07T04:42:13.000000Z","updated_at":"2025-02-01T03:27:53.000000Z","chat_mode_old":"public","chat_mode":"public","slow_mode":true,"chatable_id":145224,"followers_mode":true,"subscribers_mode":false,"emotes_mode":false,"message_interval":3,"following_min_duration":6}},"usernames":["Burger2","zigi36","Tallstack","redbug","Real_TOMMO"],"gifter_username":"BigHornsBigKnives"}
497
- LuckyUsersWhoGotGiftSubscriptionsEvent: {
498
- channel: {
499
- id: number;
500
- user_id: number;
501
- slug: string;
502
- is_banned: boolean;
503
- playback_url: string;
504
- name_updated_at: unknown;
505
- vod_enabled: boolean;
506
- subscription_enabled: boolean;
507
- is_affiliate: boolean;
508
- can_host: boolean;
509
- chatroom: {
510
- id: number;
511
- chatable_type: string;
512
- channel_id: number;
513
- created_at: string;
514
- updated_at: string;
515
- chat_mode_old: string;
516
- chat_mode: string;
517
- slow_mode: boolean;
518
- chatable_id: number;
519
- followers_mode: boolean;
520
- subscribers_mode: boolean;
521
- emotes_mode: boolean;
522
- message_interval: number;
523
- following_min_duration: number;
524
- };
525
- };
526
- usernames: string[];
527
- gifter_username: string;
528
- };
529
- }
530
-
531
- export interface ChatroomsV1Events {
532
- // {"message":{"id":"3ff9babe-b8ec-468b-aba8-f5c76986e254","numberOfViewers":1877,"optionalMessage":"","createdAt":"2025-02-10T12:03:55.204926Z"},"user":{"id":278343,"username":"Mando","isSuperAdmin":false,"verified":{"id":4867,"channel_id":275681,"created_at":"2023-11-17T20:31:43.000000Z","updated_at":"2023-11-17T20:31:43.000000Z"}}}
533
- StreamHostedEvent: {
534
- message: {
535
- createdAt: string;
536
- id: string;
537
- numberOfViewers: number;
538
- optionalMessage: string;
539
- };
540
- user: {
541
- id: number;
542
- isSuperAdmin: boolean;
543
- username: string;
544
- verified: {
545
- id: number;
546
- channel_id: number;
547
- created_at: string;
548
- updated_at: string;
549
- };
550
- };
551
- };
552
-
553
- // [message deletion event] unavailable in v1
554
-
555
- // [user ban event] unavailable in v1
556
-
557
- // {"message":{"id":"9b81e836-dfda-4824-87e3-d92e036c65d4","message":null,"type":"info","replied_to":null,"is_info":null,"link_preview":null,"chatroom_id":145222,"role":"user","created_at":1739184786,"action":"subscribe","optional_message":null,"months_subscribed":9,"subscriptions_count":0,"giftedUsers":null},"user":{"id":262477,"username":"Biceus","role":"user","isSuperAdmin":null,"profile_thumb":"https://kick-files-prod.s3.us-west-2.amazonaws.com/images/user/262477/profile_image/conversion/bdb5086d-8a01-457b-bd90-6ff2f4bfabe1-thumb.webp?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS3MDRZGPDOOAYROR%2F20250210%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250210T105306Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Signature=5740ccf2430c6b68c04fc38ff06ed47d4785f7c52b0f3855903f184bded22de4","verified":false,"follower_badges":[],"is_subscribed":null,"is_founder":false,"months_subscribed":9,"quantity_gifted":0}}
558
- ChatMessageSentEvent: {
559
- message: {
560
- action: 'subscribe' | 'gift';
561
- chatroom_id: number;
562
- created_at: number;
563
- giftedUsers: {
564
- username: string;
565
- monthsSubscribed: number;
566
- }[];
567
- id: string;
568
- is_info: unknown;
569
- link_preview: unknown;
570
- message: unknown;
571
- months_subscribed: number | null;
572
- optional_message: unknown;
573
- replied_to: unknown;
574
- role: 'user';
575
- subscriptions_count: number;
576
- type: 'info';
577
- };
578
- user: {
579
- follower_badges: unknown[];
580
- id: number;
581
- isSuperAdmin: boolean;
582
- is_founder: boolean;
583
- is_subscribed: unknown;
584
- months_subscribed: number;
585
- profile_thumb: string;
586
- quantity_gifted: number;
587
- role: 'user';
588
- username: string;
589
- verified: boolean;
590
- };
591
- };
592
- }
593
-
594
- export interface ChatroomV1Events {
595
- // {"chatroom_id":145222,"gifted_usernames":["Burger2","Real_TOMMO","Tallstack","zigi36","redbug"],"gifter_username":"BigHornsBigKnives","gifter_total":5}
596
- GiftedSubscriptionsEvent: {
597
- chatroom_id: number;
598
- gifted_usernames: string[];
599
- gifter_total: number;
600
- gifter_username: string;
601
- };
602
- }
603
-
604
- export interface ChatroomsV2Events {
605
- // {"chatroom_id":145222,"optional_message":"","number_viewers":1877,"host_username":"Mando"}
606
- StreamHostEvent: {
607
- chatroom_id: number;
608
- optional_message: string;
609
- number_viewers: number;
610
- host_username: string;
611
- };
612
-
613
- // {"id":"3b03e221-480a-4c2c-bc65-61573d3336d9","message":{"id":"499500b9-ea91-467f-9034-9eee6e6e164a"},"aiModerated":false,"violatedRules":[]}
614
- MessageDeletedEvent: {
615
- id: string;
616
- message: {
617
- id: string;
618
- };
619
- aiModerated: boolean;
620
- violatedRules: unknown[];
621
- };
622
-
623
- // {"id":"6b937ce4-dcc6-4fdf-a113-7c115e5443b1","user":{"id":24580921,"username":"helpinghand","slug":"helpinghand"},"banned_by":{"id":0,"username":"BotRix","slug":"botrix"},"permanent":false,"duration":1,"expires_at":"2025-02-10T12:19:11+00:00"}
624
- UserBannedEvent: {
625
- id: string;
626
- user: {
627
- id: number;
628
- username: string;
629
- slug: string;
630
- };
631
- banned_by: {
632
- id: number;
633
- username: string;
634
- slug: string;
635
- };
636
- permanent: boolean;
637
- duration: number;
638
- expires_at: string;
639
- };
640
-
641
- // {"chatroom_id":145222,"username":"Biceus","months":9}
642
- SubscriptionEvent: {
643
- chatroom_id: number;
644
- username: string;
645
- months: number;
646
- };
647
-
648
- ChatMessageEvent: {
649
- id: string;
650
- chatroom_id: number;
651
- content: string;
652
- type: 'message' | 'celebration';
653
- created_at: string;
654
- metadata?: {
655
- celebration: {
656
- created_at: string;
657
- id: string;
658
- total_months: number;
659
- type: 'subscription_renewed';
660
- };
661
- };
662
- sender: {
663
- id: number;
664
- username: string;
665
- slug?: string;
666
- identity: {
667
- color: string;
668
- badges: Array<{
669
- type: string;
670
- text: string;
671
- count?: number;
672
- }>;
673
- };
674
- };
675
- };
676
- }
1
+ import Pusher from 'pusher-js';
2
+
3
+ import {
4
+ type BadgeURLsByNameOrCount,
5
+ type BodyComponent,
6
+ type EmoteURLsByName,
7
+ type Message,
8
+ } from './index.js';
9
+
10
+ type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
11
+ type ConnectionStateEvent = { previous: ConnectionState; current: ConnectionState };
12
+
13
+ type EventCallbackFunctions = {
14
+ message: (message: Message) => unknown;
15
+ subscription: (data: ChatroomsV2Events['SubscriptionEvent']) => unknown;
16
+ gifted: (data: ChatroomV1Events['GiftedSubscriptionsEvent']) => unknown;
17
+ raw_message: (message: ChatMessageEvent) => unknown;
18
+ connection_state_changed: (state: ConnectionStateEvent) => unknown;
19
+ };
20
+
21
+ type EventNames = keyof EventCallbackFunctions;
22
+
23
+ const DEFAULT_KICK_PUSHER_KEY = '32cbd69e4b950bf97679';
24
+
25
+ export class KickPusher {
26
+ public kick_pusher_key = DEFAULT_KICK_PUSHER_KEY;
27
+ public channel_name?: string;
28
+ private assets: {
29
+ external_emotes: EmoteURLsByName;
30
+ badges: BadgeURLsByNameOrCount;
31
+ } = {
32
+ external_emotes: {},
33
+ badges: {
34
+ founder: '/svgs/badges/default-kick/founder.svg',
35
+ moderator: '/svgs/badges/default-kick/moderator.svg',
36
+ og: '/svgs/badges/default-kick/og.svg',
37
+ sub_gifter: {
38
+ 1: '/svgs/badges/default-kick/sub-gifter-blue.svg',
39
+ 25: '/svgs/badges/default-kick/sub-gifter-purple.svg',
40
+ 50: '/svgs/badges/default-kick/sub-gifter-red.svg',
41
+ 100: '/svgs/badges/default-kick/sub-gifter-yellow.svg',
42
+ 200: '/svgs/badges/default-kick/sub-gifter-green.svg',
43
+ },
44
+ verified: '/svgs/badges/default-kick/verified.svg',
45
+ vip: '/svgs/badges/default-kick/vip.svg',
46
+ broadcaster: '/svgs/badges/default-kick/broadcaster.svg',
47
+ staff: '/svgs/badges/default-kick/staff.svg',
48
+ },
49
+ };
50
+
51
+ private public_listeners: Partial<EventCallbackFunctions> = {};
52
+
53
+ public socket?: Pusher;
54
+ public isConnected = false;
55
+
56
+ public setBadges(badges: BadgeURLsByNameOrCount) {
57
+ this.assets.badges = { ...this.assets.badges, ...badges };
58
+ }
59
+
60
+ public setExternalEmotes(external_emotes: EmoteURLsByName) {
61
+ this.assets.external_emotes = { ...this.assets.external_emotes, ...external_emotes };
62
+ }
63
+
64
+ public getStoredBadges() {
65
+ return this.assets.badges;
66
+ }
67
+
68
+ public getStoredExternalEmotes() {
69
+ return this.assets.external_emotes;
70
+ }
71
+
72
+ public async connect(
73
+ channel?: { channelName?: string },
74
+ get_channel: (channelName: string) => Promise<GetChannelResponse | undefined> = async (
75
+ channelName,
76
+ ) => {
77
+ const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
78
+ headers: {
79
+ accept: 'aplication/json',
80
+ 'user-agent':
81
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
82
+ },
83
+ });
84
+ const json = await res.json();
85
+ return json as GetChannelResponse | undefined;
86
+ },
87
+ ) {
88
+ if (channel?.channelName) this.channel_name = channel.channelName;
89
+ if (!this.channel_name) return console.error('channel_name not specified');
90
+
91
+ console.log(`connecting to ${this.channel_name}...`);
92
+
93
+ const channel_response = await get_channel(this.channel_name);
94
+
95
+ if (!channel_response) return console.error('Failed to connect to Kick.com chat');
96
+
97
+ channel_response.subscriber_badges.forEach((subscriber_badge) => {
98
+ this.assets.badges['subscriber'] = {
99
+ ...(this.assets.badges['subscriber'] ?? {}),
100
+ [subscriber_badge.months]: subscriber_badge.badge_image.src,
101
+ };
102
+ });
103
+
104
+ this.disconnect();
105
+
106
+ this.socket = new Pusher(this.kick_pusher_key, {
107
+ cluster: 'us2',
108
+ });
109
+ /*
110
+ | 'App\\Events\\ChatMessageEvent'
111
+ | 'App\\Events\\ChatroomClearEvent'
112
+ | 'App\\Events\\ChatroomUpdatedEvent'
113
+ | 'App\\Events\\GiftedSubscriptionsEvent'
114
+ | 'App\\Events\\MessageDeletedEvent'
115
+ | 'App\\Events\\PinnedMessageCreatedEvent'
116
+ | 'App\\Events\\PinnedMessageDeletedEvent'
117
+ | 'App\\Events\\PollDeleteEvent'
118
+ | 'App\\Events\\PollUpdateEvent'
119
+ | 'App\\Events\\StreamHostEvent'
120
+ | 'App\\Events\\SubscriptionEvent'
121
+ | 'App\\Events\\UserBannedEvent'
122
+ | 'App\\Events\\UserUnbannedEvent'
123
+ */
124
+ this.socket.subscribe(`chatroom_${channel_response.chatroom.id}`);
125
+ this.socket.subscribe(`chatrooms.${channel_response.chatroom.id}.v2`);
126
+
127
+ this.socket
128
+ .bind('pusher:subscription_succeeded', () =>
129
+ this.onSubscriptionSuccess('pusher:subscription_succeeded'),
130
+ )
131
+ .bind('App\\Events\\ChatMessageEvent', (data: ChatroomsV2Events['ChatMessageEvent']) =>
132
+ this.onChatMessage(data),
133
+ )
134
+ .bind('App\\Events\\SubscriptionEvent', (data: ChatroomsV2Events['SubscriptionEvent']) =>
135
+ this.onChatSubscription(data),
136
+ )
137
+ .bind('GiftedSubscriptionsEvent', (data: ChatroomV1Events['GiftedSubscriptionsEvent']) =>
138
+ this.onChatGifted(data),
139
+ );
140
+
141
+ this.socket.connection.bind('state_change', (state: ConnectionStateEvent) => {
142
+ switch (state.current) {
143
+ case 'connected': {
144
+ this.isConnected = true;
145
+ console.log(`Connected to Kick Pusher (${this.channel_name})!`);
146
+ break;
147
+ }
148
+ case 'connecting': {
149
+ this.isConnected = false;
150
+ console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
151
+ break;
152
+ }
153
+ case 'failed': {
154
+ this.isConnected = false;
155
+ console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
156
+ break;
157
+ }
158
+ case 'unavailable': {
159
+ this.isConnected = false;
160
+ console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
161
+ break;
162
+ }
163
+ default: {
164
+ this.isConnected = false;
165
+ break;
166
+ }
167
+ }
168
+ this.public_listeners.connection_state_changed?.(state);
169
+ });
170
+ }
171
+
172
+ public disconnect() {
173
+ this.socket?.disconnect();
174
+ this.socket?.unbind_all();
175
+ }
176
+
177
+ public on<EventName extends EventNames>(
178
+ event_name: EventName,
179
+ callback_fn: EventCallbackFunctions[EventName],
180
+ ) {
181
+ this.public_listeners[event_name] = callback_fn;
182
+ }
183
+
184
+ private onSubscriptionSuccess(channel: string) {
185
+ console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
186
+ }
187
+
188
+ private onChatSubscription(data: ChatroomsV2Events['SubscriptionEvent']) {
189
+ this.public_listeners.subscription?.(data);
190
+ }
191
+
192
+ private onChatGifted(data: ChatroomV1Events['GiftedSubscriptionsEvent']) {
193
+ this.public_listeners.gifted?.(data);
194
+ }
195
+
196
+ private onChatMessage(data: ChatroomsV2Events['ChatMessageEvent']) {
197
+ this.public_listeners.raw_message?.(data);
198
+ const text = data.content;
199
+ const emote_matches = [...data.content.matchAll(/\[emote:\d+:.+\]/g)];
200
+
201
+ const body: BodyComponent[] = [];
202
+
203
+ emote_matches.forEach((match) => {
204
+ const emote_string = match[0];
205
+ const emote_parts = emote_string.slice(1, emote_string.length - 1).split(':');
206
+ const emote_id = emote_parts[1];
207
+ if (!emote_id) return;
208
+
209
+ body.push({
210
+ type: 'emote',
211
+ start_inclusive: match.index,
212
+ end_exclusive: match.index + emote_string.length,
213
+ url: `https://files.kick.com/emotes/${emote_id}/fullsize`,
214
+ });
215
+ });
216
+
217
+ body.sort((a, b) => a.start_inclusive - b.start_inclusive);
218
+
219
+ const old_body_length = body.length;
220
+
221
+ if (old_body_length > 0) {
222
+ body.forEach((segment, index) => {
223
+ const previous_segment = body[index - 1];
224
+
225
+ const text_start_inclusive =
226
+ previous_segment?.end_exclusive !== undefined ? previous_segment.end_exclusive + 1 : 0;
227
+ const text_end_exclusive = Math.max(0, segment.start_inclusive);
228
+
229
+ if (text_end_exclusive - text_start_inclusive > 0) {
230
+ body.push({
231
+ type: 'text',
232
+ text: text.slice(text_start_inclusive, text_end_exclusive),
233
+ start_inclusive: text_start_inclusive,
234
+ end_exclusive: text_end_exclusive,
235
+ });
236
+ }
237
+ if (index === old_body_length - 1 && segment.end_exclusive < text.length - 1) {
238
+ body.push({
239
+ type: 'text',
240
+ text: text.slice(segment.end_exclusive),
241
+ start_inclusive: segment.end_exclusive,
242
+ end_exclusive: text.length,
243
+ });
244
+ }
245
+ });
246
+ } else {
247
+ body.push({
248
+ type: 'text',
249
+ text,
250
+ start_inclusive: 0,
251
+ end_exclusive: text.length,
252
+ });
253
+ }
254
+
255
+ body.sort((a, b) => a.start_inclusive - b.start_inclusive);
256
+ const message: Message = {
257
+ id: data.id,
258
+ user: {
259
+ id: `${data.sender.id}`,
260
+ username: data.sender.slug ?? data.sender.username.toLowerCase(),
261
+ display_name: data.sender.username,
262
+ roles: {},
263
+ color: data.sender.identity.color,
264
+ badges: data.sender.identity.badges.flatMap((badge) => {
265
+ let badge_url: string | undefined = undefined;
266
+
267
+ const badge_url_or_counts = this.assets.badges[badge.type];
268
+ const badge_count = badge.count;
269
+
270
+ if (typeof badge_url_or_counts === 'string') badge_url = badge_url_or_counts;
271
+ else if (typeof badge_url_or_counts === 'object' && badge_count !== undefined) {
272
+ const badge_entry_by_count = Object.entries(badge_url_or_counts)
273
+ .sort(([a_min_count], [b_min_count]) => Number(a_min_count) - Number(b_min_count))
274
+ .find(([min_count]) => badge_count >= Number(min_count));
275
+ if (badge_entry_by_count) badge_url = badge_entry_by_count[1];
276
+ }
277
+
278
+ if (!badge_url) return [];
279
+ return {
280
+ set_id: badge.type,
281
+ url: badge_url,
282
+ info: String(badge.count),
283
+ };
284
+ }),
285
+ },
286
+ body,
287
+ channel: {
288
+ room_id: String(data.chatroom_id),
289
+ name: this.channel_name ?? 'unknown',
290
+ },
291
+ raw_text: data.content,
292
+ timestamp_sent: Date.parse(data.created_at),
293
+ };
294
+
295
+ if (data.type === 'celebration' && data.metadata?.celebration) {
296
+ message.resubscription = {
297
+ id: data.metadata.celebration.id,
298
+ months: data.metadata.celebration.total_months,
299
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
300
+ };
301
+ }
302
+ this.public_listeners.message?.(message);
303
+ }
304
+ }
305
+
306
+ export interface GetChannelResponse {
307
+ id: number;
308
+ user_id: number;
309
+ slug: string;
310
+ is_banned: boolean;
311
+ playback_url?: string;
312
+ vod_enabled: boolean;
313
+ subscription_enabled: boolean;
314
+ followers_count: number;
315
+ following?: boolean;
316
+ subscription?: unknown;
317
+ subscriber_badges: Array<{
318
+ id: number;
319
+ channel_id: number;
320
+ months: number;
321
+ badge_image: {
322
+ srcset: string;
323
+ src: string;
324
+ };
325
+ }>;
326
+ banner_image?: {
327
+ url: string;
328
+ };
329
+ livestream?: ChannelLivestream;
330
+ role?: unknown;
331
+ muted: boolean;
332
+ follower_badges: unknown[];
333
+ offline_banner_image: unknown;
334
+ verified: boolean;
335
+ recent_categories: Array<{
336
+ id: number;
337
+ category_id: number;
338
+ name: string;
339
+ slug: string;
340
+ tags: string[];
341
+ description?: string;
342
+ deleted_at: unknown;
343
+ viewers: number;
344
+ banner: {
345
+ responsive: string;
346
+ url: string;
347
+ };
348
+ category: {
349
+ id: number;
350
+ name: string;
351
+ slug: string;
352
+ icon: string;
353
+ };
354
+ }>;
355
+ can_host: boolean;
356
+ user: {
357
+ id: number;
358
+ username: string;
359
+ agreed_to_terms: true;
360
+ email_verified_at: Date;
361
+ bio?: string;
362
+ country?: string;
363
+ state?: string;
364
+ city?: string;
365
+ instagram?: string;
366
+ twitter?: string;
367
+ youtube?: string;
368
+ discord?: string;
369
+ tiktok?: string;
370
+ facebook?: string;
371
+ profile_pic?: string;
372
+ };
373
+ chatroom: ChannelChatroom;
374
+ ascending_links?: Array<{
375
+ id: number;
376
+ channel_id: number;
377
+ description: string;
378
+ link: string;
379
+ created_at: Date;
380
+ updated_at: Date;
381
+ order: number;
382
+ title: string;
383
+ }>;
384
+ }
385
+
386
+ export interface ChannelLivestream {
387
+ id: number;
388
+ slug: string;
389
+ channel_id: number;
390
+ created_at: Date;
391
+ session_title: string;
392
+ is_live: boolean;
393
+ risk_level_id: unknown;
394
+ start_time: Date;
395
+ source: unknown;
396
+ twitch_channel: unknown;
397
+ duration: number;
398
+ language: string;
399
+ is_mature: boolean;
400
+ viewer_count: number;
401
+ thumbnail: {
402
+ url: string;
403
+ };
404
+ categories: Array<{
405
+ id: number;
406
+ category_id: number;
407
+ name: string;
408
+ slug: string;
409
+ tags: string[];
410
+ description?: string;
411
+ deleted_at: unknown;
412
+ viewers: number;
413
+ category: {
414
+ id: number;
415
+ name: string;
416
+ slug: string;
417
+ icon: string;
418
+ };
419
+ }>;
420
+ tags: unknown[];
421
+ }
422
+ export interface ChannelChatroom {
423
+ id: number;
424
+ chatable_type: string;
425
+ channel_id: string;
426
+ created_at: Date;
427
+ updated_at: Date;
428
+ chat_mode_old: string;
429
+ chat_mode: string;
430
+ slow_mode: boolean;
431
+ chatable_id: number;
432
+ followers_mode: boolean;
433
+ subscribers_mode: boolean;
434
+ emotes_mode: boolean;
435
+ message_interval: number;
436
+ following_min_duration: number;
437
+ }
438
+ export interface ChatMessageEvent {
439
+ id: string;
440
+ chatroom_id: number;
441
+ content: string;
442
+ type: 'message' | 'celebration';
443
+ created_at: string;
444
+ metadata?: {
445
+ celebration: {
446
+ created_at: string;
447
+ id: string;
448
+ total_months: number;
449
+ type: 'subscription_renewed';
450
+ };
451
+ };
452
+ sender: {
453
+ id: number;
454
+ username: string;
455
+ slug?: string;
456
+ identity: {
457
+ color: string;
458
+ badges: Array<{
459
+ type: string;
460
+ text: string;
461
+ count?: number;
462
+ }>;
463
+ };
464
+ };
465
+ }
466
+
467
+ export interface ChannelEvents {
468
+ // {"user_ids":[262477],"username":"Biceus","channel_id":145224}
469
+ ChannelSubscriptionEvent: {
470
+ user_ids: number[];
471
+ username: string;
472
+ channel_id: number;
473
+ };
474
+ // {"leaderboard":[{"user_id":22,"username":"Eddie","quantity":2366},{"user_id":4349824,"username":"TinyTwink","quantity":1878},{"user_id":278971,"username":"Riri_The_Reducer","quantity":1171},{"user_id":703016,"username":"mewtoe","quantity":1143},{"user_id":24981650,"username":"ThailandMonger","quantity":750},{"user_id":5281070,"username":"Annoyinmous","quantity":700},{"user_id":739621,"username":"kfcbelly","quantity":621},{"user_id":367058,"username":"xWendy","quantity":512},{"user_id":35722,"username":"ABZ","quantity":509},{"user_id":723,"username":"Trainwreckstv","quantity":500}],"weekly_leaderboard":[{"user_id":11837402,"username":"BigHornsBigKnives","quantity":5},{"user_id":336982,"username":"krook","quantity":4},{"user_id":1579867,"username":"deadmoney6","quantity":1}],"monthly_leaderboard":[{"user_id":33225,"username":"MRQUICK666","quantity":10},{"user_id":8696811,"username":"Nate09","quantity":10},{"user_id":8561504,"username":"ridorana12","quantity":7},{"user_id":11837402,"username":"BigHornsBigKnives","quantity":5},{"user_id":4239451,"username":"Slippy151","quantity":5},{"user_id":48392334,"username":"isokeith","quantity":5},{"user_id":4341321,"username":"Stonkzz","quantity":5},{"user_id":16894707,"username":"Turtle427","quantity":5},{"user_id":336982,"username":"krook","quantity":4},{"user_id":76184,"username":"complicated","quantity":2}],"gifter_id":11837402,"gifter_username":"BigHornsBigKnives","gifted_quantity":5}
475
+ GiftsLeaderboardUpdated: {
476
+ leaderboard: {
477
+ user_id: number;
478
+ username: string;
479
+ quantity: number;
480
+ }[];
481
+ weekly_leaderboard: {
482
+ user_id: number;
483
+ username: string;
484
+ quantity: number;
485
+ }[];
486
+ monthly_leaderboard: {
487
+ user_id: number;
488
+ username: string;
489
+ quantity: number;
490
+ }[];
491
+ gifter_id: number;
492
+ gifter_username: string;
493
+ gifted_quantity: number;
494
+ };
495
+
496
+ // {"channel":{"id":145224,"user_id":146923,"slug":"iceposeidon","is_banned":false,"playback_url":"https://fa723fc1b171.us-west-2.playback.live-video.net/api/video/v1/us-west-2.196233775518.channel.aAMMC00nhtv0.m3u8","name_updated_at":null,"vod_enabled":true,"subscription_enabled":true,"is_affiliate":false,"can_host":true,"chatroom":{"id":145222,"chatable_type":"App\\Models\\Channel","channel_id":145224,"created_at":"2022-12-07T04:42:13.000000Z","updated_at":"2025-02-01T03:27:53.000000Z","chat_mode_old":"public","chat_mode":"public","slow_mode":true,"chatable_id":145224,"followers_mode":true,"subscribers_mode":false,"emotes_mode":false,"message_interval":3,"following_min_duration":6}},"usernames":["Burger2","zigi36","Tallstack","redbug","Real_TOMMO"],"gifter_username":"BigHornsBigKnives"}
497
+ LuckyUsersWhoGotGiftSubscriptionsEvent: {
498
+ channel: {
499
+ id: number;
500
+ user_id: number;
501
+ slug: string;
502
+ is_banned: boolean;
503
+ playback_url: string;
504
+ name_updated_at: unknown;
505
+ vod_enabled: boolean;
506
+ subscription_enabled: boolean;
507
+ is_affiliate: boolean;
508
+ can_host: boolean;
509
+ chatroom: {
510
+ id: number;
511
+ chatable_type: string;
512
+ channel_id: number;
513
+ created_at: string;
514
+ updated_at: string;
515
+ chat_mode_old: string;
516
+ chat_mode: string;
517
+ slow_mode: boolean;
518
+ chatable_id: number;
519
+ followers_mode: boolean;
520
+ subscribers_mode: boolean;
521
+ emotes_mode: boolean;
522
+ message_interval: number;
523
+ following_min_duration: number;
524
+ };
525
+ };
526
+ usernames: string[];
527
+ gifter_username: string;
528
+ };
529
+ }
530
+
531
+ export interface ChatroomsV1Events {
532
+ // {"message":{"id":"3ff9babe-b8ec-468b-aba8-f5c76986e254","numberOfViewers":1877,"optionalMessage":"","createdAt":"2025-02-10T12:03:55.204926Z"},"user":{"id":278343,"username":"Mando","isSuperAdmin":false,"verified":{"id":4867,"channel_id":275681,"created_at":"2023-11-17T20:31:43.000000Z","updated_at":"2023-11-17T20:31:43.000000Z"}}}
533
+ StreamHostedEvent: {
534
+ message: {
535
+ createdAt: string;
536
+ id: string;
537
+ numberOfViewers: number;
538
+ optionalMessage: string;
539
+ };
540
+ user: {
541
+ id: number;
542
+ isSuperAdmin: boolean;
543
+ username: string;
544
+ verified: {
545
+ id: number;
546
+ channel_id: number;
547
+ created_at: string;
548
+ updated_at: string;
549
+ };
550
+ };
551
+ };
552
+
553
+ // [message deletion event] unavailable in v1
554
+
555
+ // [user ban event] unavailable in v1
556
+
557
+ // {"message":{"id":"9b81e836-dfda-4824-87e3-d92e036c65d4","message":null,"type":"info","replied_to":null,"is_info":null,"link_preview":null,"chatroom_id":145222,"role":"user","created_at":1739184786,"action":"subscribe","optional_message":null,"months_subscribed":9,"subscriptions_count":0,"giftedUsers":null},"user":{"id":262477,"username":"Biceus","role":"user","isSuperAdmin":null,"profile_thumb":"https://kick-files-prod.s3.us-west-2.amazonaws.com/images/user/262477/profile_image/conversion/bdb5086d-8a01-457b-bd90-6ff2f4bfabe1-thumb.webp?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS3MDRZGPDOOAYROR%2F20250210%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250210T105306Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Signature=5740ccf2430c6b68c04fc38ff06ed47d4785f7c52b0f3855903f184bded22de4","verified":false,"follower_badges":[],"is_subscribed":null,"is_founder":false,"months_subscribed":9,"quantity_gifted":0}}
558
+ ChatMessageSentEvent: {
559
+ message: {
560
+ action: 'subscribe' | 'gift';
561
+ chatroom_id: number;
562
+ created_at: number;
563
+ giftedUsers: {
564
+ username: string;
565
+ monthsSubscribed: number;
566
+ }[];
567
+ id: string;
568
+ is_info: unknown;
569
+ link_preview: unknown;
570
+ message: unknown;
571
+ months_subscribed: number | null;
572
+ optional_message: unknown;
573
+ replied_to: unknown;
574
+ role: 'user';
575
+ subscriptions_count: number;
576
+ type: 'info';
577
+ };
578
+ user: {
579
+ follower_badges: unknown[];
580
+ id: number;
581
+ isSuperAdmin: boolean;
582
+ is_founder: boolean;
583
+ is_subscribed: unknown;
584
+ months_subscribed: number;
585
+ profile_thumb: string;
586
+ quantity_gifted: number;
587
+ role: 'user';
588
+ username: string;
589
+ verified: boolean;
590
+ };
591
+ };
592
+ }
593
+
594
+ export interface ChatroomV1Events {
595
+ // {"chatroom_id":145222,"gifted_usernames":["Burger2","Real_TOMMO","Tallstack","zigi36","redbug"],"gifter_username":"BigHornsBigKnives","gifter_total":5}
596
+ GiftedSubscriptionsEvent: {
597
+ chatroom_id: number;
598
+ gifted_usernames: string[];
599
+ gifter_total: number;
600
+ gifter_username: string;
601
+ };
602
+ }
603
+
604
+ export interface ChatroomsV2Events {
605
+ // {"chatroom_id":145222,"optional_message":"","number_viewers":1877,"host_username":"Mando"}
606
+ StreamHostEvent: {
607
+ chatroom_id: number;
608
+ optional_message: string;
609
+ number_viewers: number;
610
+ host_username: string;
611
+ };
612
+
613
+ // {"id":"3b03e221-480a-4c2c-bc65-61573d3336d9","message":{"id":"499500b9-ea91-467f-9034-9eee6e6e164a"},"aiModerated":false,"violatedRules":[]}
614
+ MessageDeletedEvent: {
615
+ id: string;
616
+ message: {
617
+ id: string;
618
+ };
619
+ aiModerated: boolean;
620
+ violatedRules: unknown[];
621
+ };
622
+
623
+ // {"id":"6b937ce4-dcc6-4fdf-a113-7c115e5443b1","user":{"id":24580921,"username":"helpinghand","slug":"helpinghand"},"banned_by":{"id":0,"username":"BotRix","slug":"botrix"},"permanent":false,"duration":1,"expires_at":"2025-02-10T12:19:11+00:00"}
624
+ UserBannedEvent: {
625
+ id: string;
626
+ user: {
627
+ id: number;
628
+ username: string;
629
+ slug: string;
630
+ };
631
+ banned_by: {
632
+ id: number;
633
+ username: string;
634
+ slug: string;
635
+ };
636
+ permanent: boolean;
637
+ duration: number;
638
+ expires_at: string;
639
+ };
640
+
641
+ // {"chatroom_id":145222,"username":"Biceus","months":9}
642
+ SubscriptionEvent: {
643
+ chatroom_id: number;
644
+ username: string;
645
+ months: number;
646
+ };
647
+
648
+ ChatMessageEvent: {
649
+ id: string;
650
+ chatroom_id: number;
651
+ content: string;
652
+ type: 'message' | 'celebration';
653
+ created_at: string;
654
+ metadata?: {
655
+ celebration: {
656
+ created_at: string;
657
+ id: string;
658
+ total_months: number;
659
+ type: 'subscription_renewed';
660
+ };
661
+ };
662
+ sender: {
663
+ id: number;
664
+ username: string;
665
+ slug?: string;
666
+ identity: {
667
+ color: string;
668
+ badges: Array<{
669
+ type: string;
670
+ text: string;
671
+ count?: number;
672
+ }>;
673
+ };
674
+ };
675
+ };
676
+ }