multichat-ts 0.0.87 → 0.0.90

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,474 +0,0 @@
1
- import Pusher from 'pusher-js/worker';
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: ChatSubscriptionEvent) => unknown;
16
- gifted: (data: ChatGiftedEvent) => 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
125
- .subscribe(`chatrooms.${channel_response.chatroom.id}.v2`)
126
- .bind('pusher:subscription_succeeded', () =>
127
- this.onSubscriptionSuccess('pusher:subscription_succeeded'),
128
- )
129
- .bind('App\\Events\\ChatMessageEvent', (data: ChatMessageEvent) => this.onChatMessage(data))
130
- .bind('App\\Events\\SubscriptionEvent', (data: ChatSubscriptionEvent) =>
131
- this.onChatSubscription(data),
132
- )
133
- .bind('App\\Events\\GiftedSubscriptionsEvent', (data: ChatGiftedEvent) =>
134
- this.onChatGifted(data),
135
- );
136
-
137
- this.socket.connection.bind('state_change', (state: ConnectionStateEvent) => {
138
- switch (state.current) {
139
- case 'connected': {
140
- this.isConnected = true;
141
- console.log(`Connected to Kick Pusher (${this.channel_name})!`);
142
- break;
143
- }
144
- case 'connecting': {
145
- this.isConnected = false;
146
- console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
147
- break;
148
- }
149
- case 'failed': {
150
- this.isConnected = false;
151
- console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
152
- break;
153
- }
154
- case 'unavailable': {
155
- this.isConnected = false;
156
- console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
157
- break;
158
- }
159
- default: {
160
- this.isConnected = false;
161
- break;
162
- }
163
- }
164
- this.public_listeners.connection_state_changed?.(state);
165
- });
166
- }
167
-
168
- public disconnect() {
169
- this.socket?.disconnect();
170
- this.socket?.unbind_all();
171
- }
172
-
173
- public on<EventName extends EventNames>(
174
- event_name: EventName,
175
- callback_fn: EventCallbackFunctions[EventName],
176
- ) {
177
- this.public_listeners[event_name] = callback_fn;
178
- }
179
-
180
- private onSubscriptionSuccess(channel: string) {
181
- console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
182
- }
183
-
184
- private onChatSubscription(data: ChatSubscriptionEvent) {
185
- this.public_listeners.subscription?.(data);
186
- }
187
-
188
- private onChatGifted(data: ChatGiftedEvent) {
189
- this.public_listeners.gifted?.(data);
190
- }
191
-
192
- private onChatMessage(data: ChatMessageEvent) {
193
- this.public_listeners.raw_message?.(data);
194
- const text = data.content;
195
- const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
196
-
197
- const body: BodyComponent[] = [];
198
-
199
- emote_matches.forEach((match) => {
200
- const emote_string = match[0];
201
- const emote_parts = emote_string.slice(1, emote_string.length - 1).split(':');
202
- const emote_id = emote_parts[1];
203
- if (!emote_id) return;
204
- console.log(emote_id);
205
-
206
- body.push({
207
- type: 'emote',
208
- start_inclusive: match.index,
209
- end_exclusive: match.index + emote_string.length,
210
- url: `https://files.kick.com/emotes/${emote_id}/fullsize`,
211
- });
212
- });
213
-
214
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
215
-
216
- const old_body_length = body.length;
217
-
218
- if (old_body_length > 0) {
219
- body.forEach((segment, index) => {
220
- const previous_segment = body[index - 1];
221
-
222
- const text_start_inclusive =
223
- previous_segment?.end_exclusive !== undefined ? previous_segment.end_exclusive + 1 : 0;
224
- const text_end_exclusive = Math.max(0, segment.start_inclusive);
225
-
226
- if (text_end_exclusive - text_start_inclusive > 0) {
227
- body.push({
228
- type: 'text',
229
- text: text.slice(text_start_inclusive, text_end_exclusive),
230
- start_inclusive: text_start_inclusive,
231
- end_exclusive: text_end_exclusive,
232
- });
233
- }
234
- if (index === old_body_length - 1 && segment.end_exclusive < text.length - 1) {
235
- body.push({
236
- type: 'text',
237
- text: text.slice(segment.end_exclusive),
238
- start_inclusive: segment.end_exclusive,
239
- end_exclusive: text.length,
240
- });
241
- }
242
- });
243
- } else {
244
- body.push({
245
- type: 'text',
246
- text,
247
- start_inclusive: 0,
248
- end_exclusive: text.length,
249
- });
250
- }
251
-
252
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
253
- const message: Message = {
254
- id: data.id,
255
- user: {
256
- id: `${data.sender.id}`,
257
- username: data.sender.slug ?? data.sender.username.toLowerCase(),
258
- display_name: data.sender.username,
259
- roles: {},
260
- color: data.sender.identity.color,
261
- badges: data.sender.identity.badges.flatMap((badge) => {
262
- let badge_url: string | undefined = undefined;
263
-
264
- const badge_url_or_counts = this.assets.badges[badge.type];
265
- const badge_count = badge.count;
266
-
267
- if (typeof badge_url_or_counts === 'string') badge_url = badge_url_or_counts;
268
- else if (typeof badge_url_or_counts === 'object' && badge_count !== undefined) {
269
- const badge_entry_by_count = Object.entries(badge_url_or_counts)
270
- .sort(([a_min_count], [b_min_count]) => Number(a_min_count) - Number(b_min_count))
271
- .find(([min_count]) => badge_count >= Number(min_count));
272
- if (badge_entry_by_count) badge_url = badge_entry_by_count[1];
273
- }
274
-
275
- if (!badge_url) return [];
276
- return {
277
- set_id: badge.type,
278
- url: badge_url,
279
- info: String(badge.count),
280
- };
281
- }),
282
- },
283
- body,
284
- channel: {
285
- room_id: String(data.chatroom_id),
286
- name: this.channel_name ?? 'unknown',
287
- },
288
- raw_text: data.content,
289
- timestamp_sent: Date.parse(data.created_at),
290
- };
291
-
292
- if (data.type === 'celebration' && data.metadata?.celebration) {
293
- message.resubscription = {
294
- id: data.metadata.celebration.id,
295
- months: data.metadata.celebration.total_months,
296
- subscribed_since_timestamp: data.metadata.celebration.created_at,
297
- };
298
- }
299
- this.public_listeners.message?.(message);
300
- }
301
- }
302
-
303
- export interface GetChannelResponse {
304
- id: number;
305
- user_id: number;
306
- slug: string;
307
- is_banned: boolean;
308
- playback_url?: string;
309
- vod_enabled: boolean;
310
- subscription_enabled: boolean;
311
- followers_count: number;
312
- following?: boolean;
313
- subscription?: unknown;
314
- subscriber_badges: Array<{
315
- id: number;
316
- channel_id: number;
317
- months: number;
318
- badge_image: {
319
- srcset: string;
320
- src: string;
321
- };
322
- }>;
323
- banner_image?: {
324
- url: string;
325
- };
326
- livestream?: ChannelLivestream;
327
- role?: unknown;
328
- muted: boolean;
329
- follower_badges: unknown[];
330
- offline_banner_image: unknown;
331
- verified: boolean;
332
- recent_categories: Array<{
333
- id: number;
334
- category_id: number;
335
- name: string;
336
- slug: string;
337
- tags: string[];
338
- description?: string;
339
- deleted_at: unknown;
340
- viewers: number;
341
- banner: {
342
- responsive: string;
343
- url: string;
344
- };
345
- category: {
346
- id: number;
347
- name: string;
348
- slug: string;
349
- icon: string;
350
- };
351
- }>;
352
- can_host: boolean;
353
- user: {
354
- id: number;
355
- username: string;
356
- agreed_to_terms: true;
357
- email_verified_at: Date;
358
- bio?: string;
359
- country?: string;
360
- state?: string;
361
- city?: string;
362
- instagram?: string;
363
- twitter?: string;
364
- youtube?: string;
365
- discord?: string;
366
- tiktok?: string;
367
- facebook?: string;
368
- profile_pic?: string;
369
- };
370
- chatroom: ChannelChatroom;
371
- ascending_links?: Array<{
372
- id: number;
373
- channel_id: number;
374
- description: string;
375
- link: string;
376
- created_at: Date;
377
- updated_at: Date;
378
- order: number;
379
- title: string;
380
- }>;
381
- }
382
-
383
- export interface ChannelLivestream {
384
- id: number;
385
- slug: string;
386
- channel_id: number;
387
- created_at: Date;
388
- session_title: string;
389
- is_live: boolean;
390
- risk_level_id: unknown;
391
- start_time: Date;
392
- source: unknown;
393
- twitch_channel: unknown;
394
- duration: number;
395
- language: string;
396
- is_mature: boolean;
397
- viewer_count: number;
398
- thumbnail: {
399
- url: string;
400
- };
401
- categories: Array<{
402
- id: number;
403
- category_id: number;
404
- name: string;
405
- slug: string;
406
- tags: string[];
407
- description?: string;
408
- deleted_at: unknown;
409
- viewers: number;
410
- category: {
411
- id: number;
412
- name: string;
413
- slug: string;
414
- icon: string;
415
- };
416
- }>;
417
- tags: unknown[];
418
- }
419
- export interface ChannelChatroom {
420
- id: number;
421
- chatable_type: string;
422
- channel_id: string;
423
- created_at: Date;
424
- updated_at: Date;
425
- chat_mode_old: string;
426
- chat_mode: string;
427
- slow_mode: boolean;
428
- chatable_id: number;
429
- followers_mode: boolean;
430
- subscribers_mode: boolean;
431
- emotes_mode: boolean;
432
- message_interval: number;
433
- following_min_duration: number;
434
- }
435
- export interface ChatMessageEvent {
436
- id: string;
437
- chatroom_id: number;
438
- content: string;
439
- type: 'message' | 'celebration';
440
- created_at: string;
441
- metadata?: {
442
- celebration: {
443
- created_at: string;
444
- id: string;
445
- total_months: number;
446
- type: 'subscription_renewed';
447
- };
448
- };
449
- sender: {
450
- id: number;
451
- username: string;
452
- slug?: string;
453
- identity: {
454
- color: string;
455
- badges: Array<{
456
- type: string;
457
- text: string;
458
- count?: number;
459
- }>;
460
- };
461
- };
462
- }
463
-
464
- export interface ChatSubscriptionEvent {
465
- chatroom_id: string;
466
- username: string;
467
- months: number;
468
- }
469
-
470
- export interface ChatGiftedEvent {
471
- chatroom_id: string;
472
- gifted_usernames: string[];
473
- gifter_username: string;
474
- }