multichat-ts 0.0.84 → 0.0.85

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;
@@ -20,7 +20,7 @@ export declare class KickPusher {
20
20
  getStoredExternalEmotes(): EmoteURLsByName;
21
21
  connect(channel?: {
22
22
  channelName?: string;
23
- }): Promise<void>;
23
+ }, get_channel?: (channelName: string) => Promise<GetChannelResponse | undefined>): Promise<void>;
24
24
  disconnect(): void;
25
25
  on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
26
26
  private onSubscriptionSuccess;
@@ -163,8 +163,16 @@ export interface ChatMessageEvent {
163
163
  id: string;
164
164
  chatroom_id: number;
165
165
  content: string;
166
- type: string;
166
+ type: 'message' | 'celebration';
167
167
  created_at: string;
168
+ metadata?: {
169
+ celebration: {
170
+ created_at: string;
171
+ id: string;
172
+ total_months: number;
173
+ type: 'subscription_renewed';
174
+ };
175
+ };
168
176
  sender: {
169
177
  id: number;
170
178
  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) => {
@@ -85,7 +92,6 @@ export class KickPusher {
85
92
  this.public_listeners.raw_message?.(data);
86
93
  const text = data.content;
87
94
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
88
- console.log(emote_matches);
89
95
  const body = [];
90
96
  emote_matches.forEach((match) => {
91
97
  const emote_string = match[0];
@@ -135,7 +141,7 @@ export class KickPusher {
135
141
  });
136
142
  }
137
143
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
138
- this.public_listeners.message?.({
144
+ const message = {
139
145
  id: data.id,
140
146
  user: {
141
147
  id: `${data.sender.id}`,
@@ -172,6 +178,14 @@ export class KickPusher {
172
178
  },
173
179
  raw_text: data.content,
174
180
  timestamp_sent: Date.parse(data.created_at),
175
- });
181
+ };
182
+ if (data.type === 'celebration' && data.metadata?.celebration) {
183
+ message.resubscription = {
184
+ id: data.metadata.celebration.id,
185
+ months: data.metadata.celebration.total_months,
186
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
187
+ };
188
+ }
189
+ this.public_listeners.message?.(message);
176
190
  }
177
191
  }
@@ -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;
@@ -20,7 +20,7 @@ export declare class KickPusher {
20
20
  getStoredExternalEmotes(): EmoteURLsByName;
21
21
  connect(channel?: {
22
22
  channelName?: string;
23
- }): Promise<void>;
23
+ }, get_channel?: (channelName: string) => Promise<GetChannelResponse | undefined>): Promise<void>;
24
24
  disconnect(): void;
25
25
  on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
26
26
  private onSubscriptionSuccess;
@@ -163,8 +163,16 @@ export interface ChatMessageEvent {
163
163
  id: string;
164
164
  chatroom_id: number;
165
165
  content: string;
166
- type: string;
166
+ type: 'message' | 'celebration';
167
167
  created_at: string;
168
+ metadata?: {
169
+ celebration: {
170
+ created_at: string;
171
+ id: string;
172
+ total_months: number;
173
+ type: 'subscription_renewed';
174
+ };
175
+ };
168
176
  sender: {
169
177
  id: number;
170
178
  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) => {
@@ -90,7 +92,6 @@ export class KickPusher {
90
92
  this.public_listeners.raw_message?.(data);
91
93
  const text = data.content;
92
94
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
93
- console.log(emote_matches);
94
95
  const body = [];
95
96
  emote_matches.forEach((match) => {
96
97
  const emote_string = match[0];
@@ -140,7 +141,7 @@ export class KickPusher {
140
141
  });
141
142
  }
142
143
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
143
- this.public_listeners.message?.({
144
+ const message = {
144
145
  id: data.id,
145
146
  user: {
146
147
  id: `${data.sender.id}`,
@@ -177,6 +178,14 @@ export class KickPusher {
177
178
  },
178
179
  raw_text: data.content,
179
180
  timestamp_sent: Date.parse(data.created_at),
180
- });
181
+ };
182
+ if (data.type === 'celebration' && data.metadata?.celebration) {
183
+ message.resubscription = {
184
+ id: data.metadata.celebration.id,
185
+ months: data.metadata.celebration.total_months,
186
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
187
+ };
188
+ }
189
+ this.public_listeners.message?.(message);
181
190
  }
182
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multichat-ts",
3
- "version": "0.0.84",
3
+ "version": "0.0.85",
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;
@@ -65,15 +65,28 @@ export class KickPusher {
65
65
  return this.assets.external_emotes;
66
66
  }
67
67
 
68
- public async connect(channel?: { channelName?: string }) {
68
+ public async connect(
69
+ channel?: { channelName?: string },
70
+ get_channel: (channelName: string) => Promise<GetChannelResponse | undefined> = async (
71
+ channelName,
72
+ ) => {
73
+ const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
74
+ headers: {
75
+ accept: 'aplication/json',
76
+ 'user-agent':
77
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
78
+ },
79
+ });
80
+ const json = await res.json();
81
+ return json as GetChannelResponse | undefined;
82
+ },
83
+ ) {
69
84
  if (channel?.channelName) this.channel_name = channel.channelName;
70
85
  if (!this.channel_name) return console.error('channel_name not specified');
71
86
 
72
87
  console.log(`connecting to ${this.channel_name}...`);
73
88
 
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);
89
+ const channel_response = await get_channel(this.channel_name);
77
90
 
78
91
  if (!channel_response) return console.error('Failed to connect to Kick.com chat');
79
92
 
@@ -89,7 +102,6 @@ export class KickPusher {
89
102
  this.socket = new Pusher(this.kick_pusher_key, {
90
103
  cluster: 'us2',
91
104
  });
92
-
93
105
  /*
94
106
  | 'App\\Events\\ChatMessageEvent'
95
107
  | 'App\\Events\\ChatroomClearEvent'
@@ -115,6 +127,14 @@ export class KickPusher {
115
127
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data: ChatGiftedEvent) =>
116
128
  this.onChatGifted(data),
117
129
  );
130
+
131
+ /*
132
+ {"event":"App\\Events\\ChatMessageSentEvent","data":"{\"message\":{\"id\":\"e676ab50-d59c-43ae-b7cd-2b60c5b89c08\",\"message\":null,\"type\":\"info\",\"replied_to\":null,\"is_info\":null,\"link_preview\":null,\"chatroom_id\":5755510,\"role\":\"user\",\"created_at\":1736022879,\"action\":\"gift\",\"optional_message\":null,\"months_subscribed\":null,\"subscriptions_count\":5,\"giftedUsers\":[{\"username\":\"lovesxb1\",\"monthsSubscribed\":1},{\"username\":\"O7no\",\"monthsSubscribed\":1},{\"username\":\"viiRayan\",\"monthsSubscribed\":1},{\"username\":\"Shabib_3005\",\"monthsSubscribed\":1},{\"username\":\"Khadielja\",\"monthsSubscribed\":1}]},\"user\":{\"id\":34844645,\"username\":\"Jana_ai\",\"role\":\"user\",\"isSuperAdmin\":null,\"profile_thumb\":\"https:\\/\\/kick-files-prod.s3.us-west-2.amazonaws.com\\/images\\/user\\/34844645\\/profile_image\\/conversion\\/90b8d01a-acda-4cdb-896b-8ce6d40b6767-thumb.webp?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS3MDRZGPDOOAYROR%2F20250104%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250104T203439Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Signature=ac0e479335c68c7be15861e9f78866410d64fa1627fc87433365f110901243c0\",\"verified\":false,\"follower_badges\":[],\"is_subscribed\":null,\"is_founder\":false,\"months_subscribed\":null,\"quantity_gifted\":0}}","channel":"chatrooms.5755510"}
133
+ */
134
+
135
+ /*
136
+ {"event":"App\\Events\\LuckyUsersWhoGotGiftSubscriptionsEvent","data":"{\"channel\":{\"id\":5789932,\"user_id\":5882384,\"slug\":\"sulaiman\",\"is_banned\":false,\"playback_url\":\"https:\\/\\/fa723fc1b171.us-west-2.playback.live-video.net\\/api\\/video\\/v1\\/us-west-2.196233775518.channel.L29JBfLzKok0.m3u8\",\"name_updated_at\":null,\"vod_enabled\":true,\"subscription_enabled\":true,\"is_affiliate\":false,\"can_host\":true,\"chatroom\":{\"id\":5755510,\"chatable_type\":\"App\\\\Models\\\\Channel\",\"channel_id\":5789932,\"created_at\":\"2023-06-08T08:24:23.000000Z\",\"updated_at\":\"2024-12-11T07:20:32.000000Z\",\"chat_mode_old\":\"public\",\"chat_mode\":\"public\",\"slow_mode\":false,\"chatable_id\":5789932,\"followers_mode\":true,\"subscribers_mode\":false,\"emotes_mode\":false,\"message_interval\":6,\"following_min_duration\":0}},\"usernames\":[\"lovesxb1\",\"O7no\",\"viiRayan\",\"Shabib_3005\",\"Khadielja\"],\"gifter_username\":\"Jana_ai\"}","channel":"channel.5789932"}
137
+ */
118
138
  }
119
139
 
120
140
  public disconnect() {
@@ -145,7 +165,6 @@ export class KickPusher {
145
165
  this.public_listeners.raw_message?.(data);
146
166
  const text = data.content;
147
167
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
148
- console.log(emote_matches);
149
168
 
150
169
  const body: BodyComponent[] = [];
151
170
 
@@ -203,8 +222,7 @@ export class KickPusher {
203
222
  }
204
223
 
205
224
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
206
-
207
- this.public_listeners.message?.({
225
+ const message: Message = {
208
226
  id: data.id,
209
227
  user: {
210
228
  id: `${data.sender.id}`,
@@ -241,7 +259,16 @@ export class KickPusher {
241
259
  },
242
260
  raw_text: data.content,
243
261
  timestamp_sent: Date.parse(data.created_at),
244
- });
262
+ };
263
+
264
+ if (data.type === 'celebration' && data.metadata?.celebration) {
265
+ message.resubscription = {
266
+ id: data.metadata.celebration.id,
267
+ months: data.metadata.celebration.total_months,
268
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
269
+ };
270
+ }
271
+ this.public_listeners.message?.(message);
245
272
  }
246
273
  }
247
274
 
@@ -381,8 +408,16 @@ export interface ChatMessageEvent {
381
408
  id: string;
382
409
  chatroom_id: number;
383
410
  content: string;
384
- type: string;
411
+ type: 'message' | 'celebration';
385
412
  created_at: string;
413
+ metadata?: {
414
+ celebration: {
415
+ created_at: string;
416
+ id: string;
417
+ total_months: number;
418
+ type: 'subscription_renewed';
419
+ };
420
+ };
386
421
  sender: {
387
422
  id: number;
388
423
  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;
@@ -65,21 +65,28 @@ export class KickPusher {
65
65
  return this.assets.external_emotes;
66
66
  }
67
67
 
68
- public async connect(channel?: { channelName?: string }) {
68
+ public async connect(
69
+ channel?: { channelName?: string },
70
+ get_channel: (channelName: string) => Promise<GetChannelResponse | undefined> = async (
71
+ channelName,
72
+ ) => {
73
+ const res = await fetch(`https://kick.com/api/v2/channels/${channelName}`, {
74
+ headers: {
75
+ accept: 'aplication/json',
76
+ 'user-agent':
77
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
78
+ },
79
+ });
80
+ const json = await res.json();
81
+ return json as GetChannelResponse | undefined;
82
+ },
83
+ ) {
69
84
  if (channel?.channelName) this.channel_name = channel.channelName;
70
85
  if (!this.channel_name) return console.error('channel_name not specified');
71
86
 
72
87
  console.log(`connecting to ${this.channel_name}...`);
73
88
 
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);
89
+ const channel_response = await get_channel(this.channel_name);
83
90
 
84
91
  if (!channel_response) return console.error('Failed to connect to Kick.com chat');
85
92
 
@@ -95,7 +102,6 @@ export class KickPusher {
95
102
  this.socket = new Pusher(this.kick_pusher_key, {
96
103
  cluster: 'us2',
97
104
  });
98
-
99
105
  /*
100
106
  | 'App\\Events\\ChatMessageEvent'
101
107
  | 'App\\Events\\ChatroomClearEvent'
@@ -121,6 +127,14 @@ export class KickPusher {
121
127
  .bind('App\\Events\\GiftedSubscriptionsEvent', (data: ChatGiftedEvent) =>
122
128
  this.onChatGifted(data),
123
129
  );
130
+
131
+ /*
132
+ {"event":"App\\Events\\ChatMessageSentEvent","data":"{\"message\":{\"id\":\"e676ab50-d59c-43ae-b7cd-2b60c5b89c08\",\"message\":null,\"type\":\"info\",\"replied_to\":null,\"is_info\":null,\"link_preview\":null,\"chatroom_id\":5755510,\"role\":\"user\",\"created_at\":1736022879,\"action\":\"gift\",\"optional_message\":null,\"months_subscribed\":null,\"subscriptions_count\":5,\"giftedUsers\":[{\"username\":\"lovesxb1\",\"monthsSubscribed\":1},{\"username\":\"O7no\",\"monthsSubscribed\":1},{\"username\":\"viiRayan\",\"monthsSubscribed\":1},{\"username\":\"Shabib_3005\",\"monthsSubscribed\":1},{\"username\":\"Khadielja\",\"monthsSubscribed\":1}]},\"user\":{\"id\":34844645,\"username\":\"Jana_ai\",\"role\":\"user\",\"isSuperAdmin\":null,\"profile_thumb\":\"https:\\/\\/kick-files-prod.s3.us-west-2.amazonaws.com\\/images\\/user\\/34844645\\/profile_image\\/conversion\\/90b8d01a-acda-4cdb-896b-8ce6d40b6767-thumb.webp?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS3MDRZGPDOOAYROR%2F20250104%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250104T203439Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Signature=ac0e479335c68c7be15861e9f78866410d64fa1627fc87433365f110901243c0\",\"verified\":false,\"follower_badges\":[],\"is_subscribed\":null,\"is_founder\":false,\"months_subscribed\":null,\"quantity_gifted\":0}}","channel":"chatrooms.5755510"}
133
+ */
134
+
135
+ /*
136
+ {"event":"App\\Events\\LuckyUsersWhoGotGiftSubscriptionsEvent","data":"{\"channel\":{\"id\":5789932,\"user_id\":5882384,\"slug\":\"sulaiman\",\"is_banned\":false,\"playback_url\":\"https:\\/\\/fa723fc1b171.us-west-2.playback.live-video.net\\/api\\/video\\/v1\\/us-west-2.196233775518.channel.L29JBfLzKok0.m3u8\",\"name_updated_at\":null,\"vod_enabled\":true,\"subscription_enabled\":true,\"is_affiliate\":false,\"can_host\":true,\"chatroom\":{\"id\":5755510,\"chatable_type\":\"App\\\\Models\\\\Channel\",\"channel_id\":5789932,\"created_at\":\"2023-06-08T08:24:23.000000Z\",\"updated_at\":\"2024-12-11T07:20:32.000000Z\",\"chat_mode_old\":\"public\",\"chat_mode\":\"public\",\"slow_mode\":false,\"chatable_id\":5789932,\"followers_mode\":true,\"subscribers_mode\":false,\"emotes_mode\":false,\"message_interval\":6,\"following_min_duration\":0}},\"usernames\":[\"lovesxb1\",\"O7no\",\"viiRayan\",\"Shabib_3005\",\"Khadielja\"],\"gifter_username\":\"Jana_ai\"}","channel":"channel.5789932"}
137
+ */
124
138
  }
125
139
 
126
140
  public disconnect() {
@@ -151,7 +165,6 @@ export class KickPusher {
151
165
  this.public_listeners.raw_message?.(data);
152
166
  const text = data.content;
153
167
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
154
- console.log(emote_matches);
155
168
 
156
169
  const body: BodyComponent[] = [];
157
170
 
@@ -209,8 +222,7 @@ export class KickPusher {
209
222
  }
210
223
 
211
224
  body.sort((a, b) => a.start_inclusive - b.start_inclusive);
212
-
213
- this.public_listeners.message?.({
225
+ const message: Message = {
214
226
  id: data.id,
215
227
  user: {
216
228
  id: `${data.sender.id}`,
@@ -247,7 +259,16 @@ export class KickPusher {
247
259
  },
248
260
  raw_text: data.content,
249
261
  timestamp_sent: Date.parse(data.created_at),
250
- });
262
+ };
263
+
264
+ if (data.type === 'celebration' && data.metadata?.celebration) {
265
+ message.resubscription = {
266
+ id: data.metadata.celebration.id,
267
+ months: data.metadata.celebration.total_months,
268
+ subscribed_since_timestamp: data.metadata.celebration.created_at,
269
+ };
270
+ }
271
+ this.public_listeners.message?.(message);
251
272
  }
252
273
  }
253
274
 
@@ -387,8 +408,16 @@ export interface ChatMessageEvent {
387
408
  id: string;
388
409
  chatroom_id: number;
389
410
  content: string;
390
- type: string;
411
+ type: 'message' | 'celebration';
391
412
  created_at: string;
413
+ metadata?: {
414
+ celebration: {
415
+ created_at: string;
416
+ id: string;
417
+ total_months: number;
418
+ type: 'subscription_renewed';
419
+ };
420
+ };
392
421
  sender: {
393
422
  id: number;
394
423
  username: string;