multichat-ts 0.0.84 → 0.0.86

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.
@@ -9,6 +9,11 @@ export type Message = {
9
9
  id: string;
10
10
  raw_text: string;
11
11
  timestamp_sent: number;
12
+ resubscription?: {
13
+ id: string;
14
+ months: number;
15
+ subscribed_since_timestamp: string;
16
+ };
12
17
  user: {
13
18
  badges: Badge[];
14
19
  color: string;
@@ -1,10 +1,16 @@
1
1
  import Pusher from 'pusher-js';
2
2
  import { type BadgeURLsByNameOrCount, type EmoteURLsByName, type Message } from './index.js';
3
+ type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
4
+ type ConnectionStateEvent = {
5
+ previous: ConnectionState;
6
+ current: ConnectionState;
7
+ };
3
8
  type EventCallbackFunctions = {
4
9
  message: (message: Message) => unknown;
5
10
  subscription: (data: ChatSubscriptionEvent) => unknown;
6
11
  gifted: (data: ChatGiftedEvent) => unknown;
7
12
  raw_message: (message: ChatMessageEvent) => unknown;
13
+ connection_state_changed: (state: ConnectionStateEvent) => unknown;
8
14
  };
9
15
  type EventNames = keyof EventCallbackFunctions;
10
16
  export declare class KickPusher {
@@ -20,7 +26,7 @@ export declare class KickPusher {
20
26
  getStoredExternalEmotes(): EmoteURLsByName;
21
27
  connect(channel?: {
22
28
  channelName?: string;
23
- }): Promise<void>;
29
+ }, get_channel?: (channelName: string) => Promise<GetChannelResponse | undefined>): Promise<void>;
24
30
  disconnect(): void;
25
31
  on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
26
32
  private onSubscriptionSuccess;
@@ -163,8 +169,16 @@ export interface ChatMessageEvent {
163
169
  id: string;
164
170
  chatroom_id: number;
165
171
  content: string;
166
- type: string;
172
+ type: 'message' | 'celebration';
167
173
  created_at: string;
174
+ metadata?: {
175
+ celebration: {
176
+ created_at: string;
177
+ id: string;
178
+ total_months: number;
179
+ type: 'subscription_renewed';
180
+ };
181
+ };
168
182
  sender: {
169
183
  id: number;
170
184
  username: string;
@@ -37,15 +37,22 @@ export class KickPusher {
37
37
  getStoredExternalEmotes() {
38
38
  return this.assets.external_emotes;
39
39
  }
40
- async connect(channel) {
40
+ async connect(channel, get_channel = async (channelName) => {
41
+ const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
42
+ headers: {
43
+ accept: 'aplication/json',
44
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
45
+ },
46
+ });
47
+ const json = await res.json();
48
+ return json;
49
+ }) {
41
50
  if (channel?.channelName)
42
51
  this.channel_name = channel.channelName;
43
52
  if (!this.channel_name)
44
53
  return console.error('channel_name not specified');
45
54
  console.log(`connecting to ${this.channel_name}...`);
46
- const channel_response = await fetch(`https://kick.com/api/v2/channels/${this.channel_name}`)
47
- .then((res) => res.json())
48
- .then((json) => json);
55
+ const channel_response = await get_channel(this.channel_name);
49
56
  if (!channel_response)
50
57
  return console.error('Failed to connect to Kick.com chat');
51
58
  channel_response.subscriber_badges.forEach((subscriber_badge) => {
@@ -60,10 +67,31 @@ export class KickPusher {
60
67
  });
61
68
  this.socket
62
69
  .subscribe(`chatrooms.${channel_response.chatroom.id}.v2`)
63
- .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess())
70
+ .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess('pusher:subscription_succeeded'))
64
71
  .bind('App\\Events\\ChatMessageEvent', (data) => this.onChatMessage(data))
65
72
  .bind('App\\Events\\SubscriptionEvent', (data) => this.onChatSubscription(data))
66
73
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data) => this.onChatGifted(data));
74
+ this.socket.connection.bind('state_change', (state) => {
75
+ switch (state.current) {
76
+ case 'connected': {
77
+ console.log(`Connected to Kick Pusher (${this.channel_name})!`);
78
+ break;
79
+ }
80
+ case 'connecting': {
81
+ console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
82
+ break;
83
+ }
84
+ case 'failed': {
85
+ console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
86
+ break;
87
+ }
88
+ case 'unavailable': {
89
+ console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
90
+ break;
91
+ }
92
+ }
93
+ this.public_listeners.connection_state_changed?.(state);
94
+ });
67
95
  }
68
96
  disconnect() {
69
97
  this.socket?.disconnect();
@@ -72,8 +100,8 @@ export class KickPusher {
72
100
  on(event_name, callback_fn) {
73
101
  this.public_listeners[event_name] = callback_fn;
74
102
  }
75
- onSubscriptionSuccess() {
76
- console.log(`Connected to Kick Pusher (${this.channel_name})`);
103
+ onSubscriptionSuccess(channel) {
104
+ console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
77
105
  }
78
106
  onChatSubscription(data) {
79
107
  this.public_listeners.subscription?.(data);
@@ -85,7 +113,6 @@ export class KickPusher {
85
113
  this.public_listeners.raw_message?.(data);
86
114
  const text = data.content;
87
115
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
88
- console.log(emote_matches);
89
116
  const body = [];
90
117
  emote_matches.forEach((match) => {
91
118
  const emote_string = match[0];
@@ -135,7 +162,7 @@ export class KickPusher {
135
162
  });
136
163
  }
137
164
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
138
- this.public_listeners.message?.({
165
+ const message = {
139
166
  id: data.id,
140
167
  user: {
141
168
  id: `${data.sender.id}`,
@@ -172,6 +199,14 @@ export class KickPusher {
172
199
  },
173
200
  raw_text: data.content,
174
201
  timestamp_sent: Date.parse(data.created_at),
175
- });
202
+ };
203
+ if (data.type === 'celebration' && data.metadata?.celebration) {
204
+ message.resubscription = {
205
+ id: data.metadata.celebration.id,
206
+ months: data.metadata.celebration.total_months,
207
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
208
+ };
209
+ }
210
+ this.public_listeners.message?.(message);
176
211
  }
177
212
  }
@@ -9,6 +9,11 @@ export type Message = {
9
9
  id: string;
10
10
  raw_text: string;
11
11
  timestamp_sent: number;
12
+ resubscription?: {
13
+ id: string;
14
+ months: number;
15
+ subscribed_since_timestamp: string;
16
+ };
12
17
  user: {
13
18
  badges: Badge[];
14
19
  color: string;
@@ -1,10 +1,16 @@
1
1
  import Pusher from 'pusher-js/worker';
2
2
  import { type BadgeURLsByNameOrCount, type EmoteURLsByName, type Message } from './index.js';
3
+ type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
4
+ type ConnectionStateEvent = {
5
+ previous: ConnectionState;
6
+ current: ConnectionState;
7
+ };
3
8
  type EventCallbackFunctions = {
4
9
  message: (message: Message) => unknown;
5
10
  subscription: (data: ChatSubscriptionEvent) => unknown;
6
11
  gifted: (data: ChatGiftedEvent) => unknown;
7
12
  raw_message: (message: ChatMessageEvent) => unknown;
13
+ connection_state_changed: (state: ConnectionStateEvent) => unknown;
8
14
  };
9
15
  type EventNames = keyof EventCallbackFunctions;
10
16
  export declare class KickPusher {
@@ -20,7 +26,7 @@ export declare class KickPusher {
20
26
  getStoredExternalEmotes(): EmoteURLsByName;
21
27
  connect(channel?: {
22
28
  channelName?: string;
23
- }): Promise<void>;
29
+ }, get_channel?: (channelName: string) => Promise<GetChannelResponse | undefined>): Promise<void>;
24
30
  disconnect(): void;
25
31
  on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
26
32
  private onSubscriptionSuccess;
@@ -163,8 +169,16 @@ export interface ChatMessageEvent {
163
169
  id: string;
164
170
  chatroom_id: number;
165
171
  content: string;
166
- type: string;
172
+ type: 'message' | 'celebration';
167
173
  created_at: string;
174
+ metadata?: {
175
+ celebration: {
176
+ created_at: string;
177
+ id: string;
178
+ total_months: number;
179
+ type: 'subscription_renewed';
180
+ };
181
+ };
168
182
  sender: {
169
183
  id: number;
170
184
  username: string;
@@ -37,20 +37,22 @@ export class KickPusher {
37
37
  getStoredExternalEmotes() {
38
38
  return this.assets.external_emotes;
39
39
  }
40
- async connect(channel) {
40
+ async connect(channel, get_channel = async (channelName) => {
41
+ const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
42
+ headers: {
43
+ accept: 'aplication/json',
44
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
45
+ },
46
+ });
47
+ const json = await res.json();
48
+ return json;
49
+ }) {
41
50
  if (channel?.channelName)
42
51
  this.channel_name = channel.channelName;
43
52
  if (!this.channel_name)
44
53
  return console.error('channel_name not specified');
45
54
  console.log(`connecting to ${this.channel_name}...`);
46
- const channel_response = await fetch(`https://kick.com/api/v2/channels/${this.channel_name}`, {
47
- headers: {
48
- accept: 'aplication/json',
49
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
50
- },
51
- })
52
- .then((res) => res.json())
53
- .then((json) => json);
55
+ const channel_response = await get_channel(this.channel_name);
54
56
  if (!channel_response)
55
57
  return console.error('Failed to connect to Kick.com chat');
56
58
  channel_response.subscriber_badges.forEach((subscriber_badge) => {
@@ -65,10 +67,31 @@ export class KickPusher {
65
67
  });
66
68
  this.socket
67
69
  .subscribe(`chatrooms.${channel_response.chatroom.id}.v2`)
68
- .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess())
70
+ .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess('pusher:subscription_succeeded'))
69
71
  .bind('App\\Events\\ChatMessageEvent', (data) => this.onChatMessage(data))
70
72
  .bind('App\\Events\\SubscriptionEvent', (data) => this.onChatSubscription(data))
71
73
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data) => this.onChatGifted(data));
74
+ this.socket.connection.bind('state_change', (state) => {
75
+ switch (state.current) {
76
+ case 'connected': {
77
+ console.log(`Connected to Kick Pusher (${this.channel_name})!`);
78
+ break;
79
+ }
80
+ case 'connecting': {
81
+ console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
82
+ break;
83
+ }
84
+ case 'failed': {
85
+ console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
86
+ break;
87
+ }
88
+ case 'unavailable': {
89
+ console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
90
+ break;
91
+ }
92
+ }
93
+ this.public_listeners.connection_state_changed?.(state);
94
+ });
72
95
  }
73
96
  disconnect() {
74
97
  this.socket?.disconnect();
@@ -77,8 +100,8 @@ export class KickPusher {
77
100
  on(event_name, callback_fn) {
78
101
  this.public_listeners[event_name] = callback_fn;
79
102
  }
80
- onSubscriptionSuccess() {
81
- console.log(`Connected to Kick Pusher (${this.channel_name})`);
103
+ onSubscriptionSuccess(channel) {
104
+ console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
82
105
  }
83
106
  onChatSubscription(data) {
84
107
  this.public_listeners.subscription?.(data);
@@ -90,7 +113,6 @@ export class KickPusher {
90
113
  this.public_listeners.raw_message?.(data);
91
114
  const text = data.content;
92
115
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
93
- console.log(emote_matches);
94
116
  const body = [];
95
117
  emote_matches.forEach((match) => {
96
118
  const emote_string = match[0];
@@ -140,7 +162,7 @@ export class KickPusher {
140
162
  });
141
163
  }
142
164
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
143
- this.public_listeners.message?.({
165
+ const message = {
144
166
  id: data.id,
145
167
  user: {
146
168
  id: `${data.sender.id}`,
@@ -177,6 +199,14 @@ export class KickPusher {
177
199
  },
178
200
  raw_text: data.content,
179
201
  timestamp_sent: Date.parse(data.created_at),
180
- });
202
+ };
203
+ if (data.type === 'celebration' && data.metadata?.celebration) {
204
+ message.resubscription = {
205
+ id: data.metadata.celebration.id,
206
+ months: data.metadata.celebration.total_months,
207
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
208
+ };
209
+ }
210
+ this.public_listeners.message?.(message);
181
211
  }
182
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multichat-ts",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "type": "module",
5
5
  "description": "Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)",
6
6
  "repository": {
@@ -10,6 +10,11 @@ export type Message = {
10
10
  id: string;
11
11
  raw_text: string;
12
12
  timestamp_sent: number;
13
+ resubscription?: {
14
+ id: string;
15
+ months: number;
16
+ subscribed_since_timestamp: string;
17
+ };
13
18
  user: {
14
19
  badges: Badge[];
15
20
  color: string;
@@ -7,11 +7,15 @@ import {
7
7
  type Message,
8
8
  } from './index.js';
9
9
 
10
+ type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
11
+ type ConnectionStateEvent = { previous: ConnectionState; current: ConnectionState };
12
+
10
13
  type EventCallbackFunctions = {
11
14
  message: (message: Message) => unknown;
12
15
  subscription: (data: ChatSubscriptionEvent) => unknown;
13
16
  gifted: (data: ChatGiftedEvent) => unknown;
14
17
  raw_message: (message: ChatMessageEvent) => unknown;
18
+ connection_state_changed: (state: ConnectionStateEvent) => unknown;
15
19
  };
16
20
 
17
21
  type EventNames = keyof EventCallbackFunctions;
@@ -65,15 +69,28 @@ export class KickPusher {
65
69
  return this.assets.external_emotes;
66
70
  }
67
71
 
68
- public async connect(channel?: { channelName?: string }) {
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
+ ) {
69
88
  if (channel?.channelName) this.channel_name = channel.channelName;
70
89
  if (!this.channel_name) return console.error('channel_name not specified');
71
90
 
72
91
  console.log(`connecting to ${this.channel_name}...`);
73
92
 
74
- const channel_response = await fetch(`https://kick.com/api/v2/channels/${this.channel_name}`)
75
- .then((res) => res.json())
76
- .then((json) => json as GetChannelResponse | undefined);
93
+ const channel_response = await get_channel(this.channel_name);
77
94
 
78
95
  if (!channel_response) return console.error('Failed to connect to Kick.com chat');
79
96
 
@@ -89,7 +106,6 @@ export class KickPusher {
89
106
  this.socket = new Pusher(this.kick_pusher_key, {
90
107
  cluster: 'us2',
91
108
  });
92
-
93
109
  /*
94
110
  | 'App\\Events\\ChatMessageEvent'
95
111
  | 'App\\Events\\ChatroomClearEvent'
@@ -107,7 +123,9 @@ export class KickPusher {
107
123
  */
108
124
  this.socket
109
125
  .subscribe(`chatrooms.${channel_response.chatroom.id}.v2`)
110
- .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess())
126
+ .bind('pusher:subscription_succeeded', () =>
127
+ this.onSubscriptionSuccess('pusher:subscription_succeeded'),
128
+ )
111
129
  .bind('App\\Events\\ChatMessageEvent', (data: ChatMessageEvent) => this.onChatMessage(data))
112
130
  .bind('App\\Events\\SubscriptionEvent', (data: ChatSubscriptionEvent) =>
113
131
  this.onChatSubscription(data),
@@ -115,6 +133,28 @@ export class KickPusher {
115
133
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data: ChatGiftedEvent) =>
116
134
  this.onChatGifted(data),
117
135
  );
136
+
137
+ this.socket.connection.bind('state_change', (state: ConnectionStateEvent) => {
138
+ switch (state.current) {
139
+ case 'connected': {
140
+ console.log(`Connected to Kick Pusher (${this.channel_name})!`);
141
+ break;
142
+ }
143
+ case 'connecting': {
144
+ console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
145
+ break;
146
+ }
147
+ case 'failed': {
148
+ console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
149
+ break;
150
+ }
151
+ case 'unavailable': {
152
+ console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
153
+ break;
154
+ }
155
+ }
156
+ this.public_listeners.connection_state_changed?.(state);
157
+ });
118
158
  }
119
159
 
120
160
  public disconnect() {
@@ -129,8 +169,8 @@ export class KickPusher {
129
169
  this.public_listeners[event_name] = callback_fn;
130
170
  }
131
171
 
132
- private onSubscriptionSuccess() {
133
- console.log(`Connected to Kick Pusher (${this.channel_name})`);
172
+ private onSubscriptionSuccess(channel: string) {
173
+ console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
134
174
  }
135
175
 
136
176
  private onChatSubscription(data: ChatSubscriptionEvent) {
@@ -145,7 +185,6 @@ export class KickPusher {
145
185
  this.public_listeners.raw_message?.(data);
146
186
  const text = data.content;
147
187
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
148
- console.log(emote_matches);
149
188
 
150
189
  const body: BodyComponent[] = [];
151
190
 
@@ -203,8 +242,7 @@ export class KickPusher {
203
242
  }
204
243
 
205
244
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
206
-
207
- this.public_listeners.message?.({
245
+ const message: Message = {
208
246
  id: data.id,
209
247
  user: {
210
248
  id: `${data.sender.id}`,
@@ -241,7 +279,16 @@ export class KickPusher {
241
279
  },
242
280
  raw_text: data.content,
243
281
  timestamp_sent: Date.parse(data.created_at),
244
- });
282
+ };
283
+
284
+ if (data.type === 'celebration' && data.metadata?.celebration) {
285
+ message.resubscription = {
286
+ id: data.metadata.celebration.id,
287
+ months: data.metadata.celebration.total_months,
288
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
289
+ };
290
+ }
291
+ this.public_listeners.message?.(message);
245
292
  }
246
293
  }
247
294
 
@@ -381,8 +428,16 @@ export interface ChatMessageEvent {
381
428
  id: string;
382
429
  chatroom_id: number;
383
430
  content: string;
384
- type: string;
431
+ type: 'message' | 'celebration';
385
432
  created_at: string;
433
+ metadata?: {
434
+ celebration: {
435
+ created_at: string;
436
+ id: string;
437
+ total_months: number;
438
+ type: 'subscription_renewed';
439
+ };
440
+ };
386
441
  sender: {
387
442
  id: number;
388
443
  username: string;
@@ -10,6 +10,11 @@ export type Message = {
10
10
  id: string;
11
11
  raw_text: string;
12
12
  timestamp_sent: number;
13
+ resubscription?: {
14
+ id: string;
15
+ months: number;
16
+ subscribed_since_timestamp: string;
17
+ };
13
18
  user: {
14
19
  badges: Badge[];
15
20
  color: string;
@@ -7,11 +7,15 @@ import {
7
7
  type Message,
8
8
  } from './index.js';
9
9
 
10
+ type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed';
11
+ type ConnectionStateEvent = { previous: ConnectionState; current: ConnectionState };
12
+
10
13
  type EventCallbackFunctions = {
11
14
  message: (message: Message) => unknown;
12
15
  subscription: (data: ChatSubscriptionEvent) => unknown;
13
16
  gifted: (data: ChatGiftedEvent) => unknown;
14
17
  raw_message: (message: ChatMessageEvent) => unknown;
18
+ connection_state_changed: (state: ConnectionStateEvent) => unknown;
15
19
  };
16
20
 
17
21
  type EventNames = keyof EventCallbackFunctions;
@@ -65,21 +69,28 @@ export class KickPusher {
65
69
  return this.assets.external_emotes;
66
70
  }
67
71
 
68
- public async connect(channel?: { channelName?: string }) {
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
+ ) {
69
88
  if (channel?.channelName) this.channel_name = channel.channelName;
70
89
  if (!this.channel_name) return console.error('channel_name not specified');
71
90
 
72
91
  console.log(`connecting to ${this.channel_name}...`);
73
92
 
74
- const channel_response = await fetch(`https://kick.com/api/v2/channels/${this.channel_name}`, {
75
- headers: {
76
- accept: 'aplication/json',
77
- 'user-agent':
78
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
79
- },
80
- })
81
- .then((res) => res.json())
82
- .then((json) => json as GetChannelResponse | undefined);
93
+ const channel_response = await get_channel(this.channel_name);
83
94
 
84
95
  if (!channel_response) return console.error('Failed to connect to Kick.com chat');
85
96
 
@@ -95,7 +106,6 @@ export class KickPusher {
95
106
  this.socket = new Pusher(this.kick_pusher_key, {
96
107
  cluster: 'us2',
97
108
  });
98
-
99
109
  /*
100
110
  | 'App\\Events\\ChatMessageEvent'
101
111
  | 'App\\Events\\ChatroomClearEvent'
@@ -113,7 +123,9 @@ export class KickPusher {
113
123
  */
114
124
  this.socket
115
125
  .subscribe(`chatrooms.${channel_response.chatroom.id}.v2`)
116
- .bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess())
126
+ .bind('pusher:subscription_succeeded', () =>
127
+ this.onSubscriptionSuccess('pusher:subscription_succeeded'),
128
+ )
117
129
  .bind('App\\Events\\ChatMessageEvent', (data: ChatMessageEvent) => this.onChatMessage(data))
118
130
  .bind('App\\Events\\SubscriptionEvent', (data: ChatSubscriptionEvent) =>
119
131
  this.onChatSubscription(data),
@@ -121,6 +133,28 @@ export class KickPusher {
121
133
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data: ChatGiftedEvent) =>
122
134
  this.onChatGifted(data),
123
135
  );
136
+
137
+ this.socket.connection.bind('state_change', (state: ConnectionStateEvent) => {
138
+ switch (state.current) {
139
+ case 'connected': {
140
+ console.log(`Connected to Kick Pusher (${this.channel_name})!`);
141
+ break;
142
+ }
143
+ case 'connecting': {
144
+ console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
145
+ break;
146
+ }
147
+ case 'failed': {
148
+ console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
149
+ break;
150
+ }
151
+ case 'unavailable': {
152
+ console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
153
+ break;
154
+ }
155
+ }
156
+ this.public_listeners.connection_state_changed?.(state);
157
+ });
124
158
  }
125
159
 
126
160
  public disconnect() {
@@ -135,8 +169,8 @@ export class KickPusher {
135
169
  this.public_listeners[event_name] = callback_fn;
136
170
  }
137
171
 
138
- private onSubscriptionSuccess() {
139
- console.log(`Connected to Kick Pusher (${this.channel_name})`);
172
+ private onSubscriptionSuccess(channel: string) {
173
+ console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
140
174
  }
141
175
 
142
176
  private onChatSubscription(data: ChatSubscriptionEvent) {
@@ -151,7 +185,6 @@ export class KickPusher {
151
185
  this.public_listeners.raw_message?.(data);
152
186
  const text = data.content;
153
187
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
154
- console.log(emote_matches);
155
188
 
156
189
  const body: BodyComponent[] = [];
157
190
 
@@ -209,8 +242,7 @@ export class KickPusher {
209
242
  }
210
243
 
211
244
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
212
-
213
- this.public_listeners.message?.({
245
+ const message: Message = {
214
246
  id: data.id,
215
247
  user: {
216
248
  id: `${data.sender.id}`,
@@ -247,7 +279,16 @@ export class KickPusher {
247
279
  },
248
280
  raw_text: data.content,
249
281
  timestamp_sent: Date.parse(data.created_at),
250
- });
282
+ };
283
+
284
+ if (data.type === 'celebration' && data.metadata?.celebration) {
285
+ message.resubscription = {
286
+ id: data.metadata.celebration.id,
287
+ months: data.metadata.celebration.total_months,
288
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
289
+ };
290
+ }
291
+ this.public_listeners.message?.(message);
251
292
  }
252
293
  }
253
294
 
@@ -387,8 +428,16 @@ export interface ChatMessageEvent {
387
428
  id: string;
388
429
  chatroom_id: number;
389
430
  content: string;
390
- type: string;
431
+ type: 'message' | 'celebration';
391
432
  created_at: string;
433
+ metadata?: {
434
+ celebration: {
435
+ created_at: string;
436
+ id: string;
437
+ total_months: number;
438
+ type: 'subscription_renewed';
439
+ };
440
+ };
392
441
  sender: {
393
442
  id: number;
394
443
  username: string;