multichat-ts 0.0.95 → 0.0.96
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.
- package/dist/kick.d.ts +5 -3
- package/dist/kick.js +202 -123
- package/dist/safety.d.ts +5 -0
- package/dist/safety.js +56 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +24 -0
- package/dist/twitch.d.ts +17 -35
- package/dist/twitch.js +334 -374
- package/package.json +1 -1
- package/src/default/kick.ts +277 -159
- package/src/default/safety.ts +72 -0
- package/src/default/test.ts +32 -0
- package/src/default/twitch.ts +411 -428
package/dist/kick.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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';
|
|
3
|
+
type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed' | 'disconnected';
|
|
4
4
|
type ConnectionStateEvent = {
|
|
5
5
|
previous: ConnectionState;
|
|
6
6
|
current: ConnectionState;
|
|
@@ -18,15 +18,17 @@ export declare class KickPusher {
|
|
|
18
18
|
channel_name?: string;
|
|
19
19
|
private assets;
|
|
20
20
|
private public_listeners;
|
|
21
|
-
socket?: Pusher;
|
|
21
|
+
socket?: Pusher | undefined;
|
|
22
22
|
isConnected: boolean;
|
|
23
23
|
setBadges(badges: BadgeURLsByNameOrCount): void;
|
|
24
24
|
setExternalEmotes(external_emotes: EmoteURLsByName): void;
|
|
25
25
|
getStoredBadges(): BadgeURLsByNameOrCount;
|
|
26
26
|
getStoredExternalEmotes(): EmoteURLsByName;
|
|
27
|
+
private connectionVersion;
|
|
28
|
+
private closeSocket;
|
|
27
29
|
connect(channel?: {
|
|
28
30
|
channelName?: string;
|
|
29
|
-
},
|
|
31
|
+
}, getChannel?: (channelName: string) => Promise<GetChannelResponse | undefined>): Promise<void>;
|
|
30
32
|
disconnect(): void;
|
|
31
33
|
on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
|
|
32
34
|
private onSubscriptionSuccess;
|
package/dist/kick.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Pusher from 'pusher-js';
|
|
2
|
+
import { buildMessageBody, isFiniteNumber, isRecord, runSafely } from './safety.js';
|
|
2
3
|
const DEFAULT_KICK_PUSHER_KEY = '32cbd69e4b950bf97679';
|
|
3
4
|
export class KickPusher {
|
|
4
5
|
kick_pusher_key = DEFAULT_KICK_PUSHER_KEY;
|
|
@@ -37,74 +38,104 @@ export class KickPusher {
|
|
|
37
38
|
getStoredExternalEmotes() {
|
|
38
39
|
return this.assets.external_emotes;
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
connectionVersion = 0;
|
|
42
|
+
closeSocket() {
|
|
43
|
+
const socket = this.socket;
|
|
44
|
+
this.socket = undefined;
|
|
45
|
+
this.isConnected = false;
|
|
46
|
+
if (!socket)
|
|
47
|
+
return;
|
|
48
|
+
runSafely('kick.cleanup.connection', () => socket.connection.unbind_all());
|
|
49
|
+
runSafely('kick.cleanup.listeners', () => socket.unbind_all());
|
|
50
|
+
runSafely('kick.cleanup.disconnect', () => socket.disconnect());
|
|
51
|
+
}
|
|
52
|
+
async connect(channel, getChannel = defaultGetChannel) {
|
|
53
|
+
if (channel?.channelName) {
|
|
51
54
|
this.channel_name = channel.channelName;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.socket
|
|
71
|
-
.bind('pusher:subscription_succeeded', () => this.onSubscriptionSuccess('pusher:subscription_succeeded'))
|
|
72
|
-
.bind('App\\Events\\ChatMessageEvent', (data) => this.onChatMessage(data))
|
|
73
|
-
.bind('App\\Events\\SubscriptionEvent', (data) => this.onChatSubscription(data))
|
|
74
|
-
.bind('GiftedSubscriptionsEvent', (data) => this.onChatGifted(data));
|
|
75
|
-
this.socket.connection.bind('state_change', (state) => {
|
|
76
|
-
switch (state.current) {
|
|
77
|
-
case 'connected': {
|
|
78
|
-
this.isConnected = true;
|
|
79
|
-
console.log(`Connected to Kick Pusher (${this.channel_name})!`);
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
case 'connecting': {
|
|
83
|
-
this.isConnected = false;
|
|
84
|
-
console.log(`Connecting to Kick Pusher (${this.channel_name})...`);
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
case 'failed': {
|
|
88
|
-
this.isConnected = false;
|
|
89
|
-
console.log(`Failed to connect to Kick Pusher (${this.channel_name})`);
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
case 'unavailable': {
|
|
93
|
-
this.isConnected = false;
|
|
94
|
-
console.log(`Disconnected from Kick Pusher (${this.channel_name})`);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
default: {
|
|
98
|
-
this.isConnected = false;
|
|
99
|
-
break;
|
|
55
|
+
}
|
|
56
|
+
const channelName = this.channel_name;
|
|
57
|
+
if (!channelName) {
|
|
58
|
+
throw new Error('Kick channel_name not specified');
|
|
59
|
+
}
|
|
60
|
+
const version = ++this.connectionVersion;
|
|
61
|
+
this.closeSocket();
|
|
62
|
+
const response = await getChannel(channelName);
|
|
63
|
+
if (version !== this.connectionVersion)
|
|
64
|
+
return;
|
|
65
|
+
if (!response || !isFiniteNumber(response.chatroom?.id) || response.chatroom.id <= 0) {
|
|
66
|
+
throw new Error(`Invalid Kick channel response: ${channelName}`);
|
|
67
|
+
}
|
|
68
|
+
const subscriberBadges = {};
|
|
69
|
+
if (Array.isArray(response.subscriber_badges)) {
|
|
70
|
+
for (const badge of response.subscriber_badges) {
|
|
71
|
+
if (badge && isFiniteNumber(badge.months) && typeof badge.badge_image?.src === 'string') {
|
|
72
|
+
subscriberBadges[badge.months] = badge.badge_image.src;
|
|
100
73
|
}
|
|
101
74
|
}
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
}
|
|
76
|
+
this.assets.badges['subscriber'] = subscriberBadges;
|
|
77
|
+
try {
|
|
78
|
+
const socket = new Pusher(this.kick_pusher_key, {
|
|
79
|
+
cluster: 'us2',
|
|
80
|
+
});
|
|
81
|
+
this.socket = socket;
|
|
82
|
+
const bind = (event, handler) => {
|
|
83
|
+
socket.bind(event, (data) => {
|
|
84
|
+
if (this.socket !== socket)
|
|
85
|
+
return;
|
|
86
|
+
runSafely(`kick.${event}`, () => handler(data));
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
bind('App\\Events\\ChatMessageEvent', (data) => this.onChatMessage(data));
|
|
90
|
+
bind('App\\Events\\SubscriptionEvent', (data) => this.onChatSubscription(data));
|
|
91
|
+
bind('GiftedSubscriptionsEvent', (data) => this.onChatGifted(data));
|
|
92
|
+
socket.connection.bind('state_change', (state) => {
|
|
93
|
+
if (this.socket !== socket)
|
|
94
|
+
return;
|
|
95
|
+
runSafely('kick.state_change', () => {
|
|
96
|
+
if (!state || typeof state.current !== 'string') {
|
|
97
|
+
throw new Error('Invalid Pusher state-change payload');
|
|
98
|
+
}
|
|
99
|
+
this.isConnected = state.current === 'connected';
|
|
100
|
+
runSafely('kick.connection_state_changed', () => this.public_listeners.connection_state_changed?.(state));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
socket.connection.bind('error', (error) => {
|
|
104
|
+
if (this.socket !== socket)
|
|
105
|
+
return;
|
|
106
|
+
console.error('Kick Pusher connection error', {
|
|
107
|
+
channelName,
|
|
108
|
+
error,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
const names = [`chatroom_${response.chatroom.id}`, `chatrooms.${response.chatroom.id}.v2`];
|
|
112
|
+
for (const name of names) {
|
|
113
|
+
const subscription = socket.subscribe(name);
|
|
114
|
+
subscription.bind('pusher:subscription_succeeded', () => {
|
|
115
|
+
if (this.socket !== socket)
|
|
116
|
+
return;
|
|
117
|
+
runSafely('kick.subscription_succeeded', () => this.onSubscriptionSuccess(name));
|
|
118
|
+
});
|
|
119
|
+
subscription.bind('pusher:subscription_error', (error) => {
|
|
120
|
+
if (this.socket !== socket)
|
|
121
|
+
return;
|
|
122
|
+
console.error('Kick Pusher subscription error', {
|
|
123
|
+
channelName,
|
|
124
|
+
subscription: name,
|
|
125
|
+
error,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
this.isConnected = socket.connection.state === 'connected';
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.closeSocket();
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
104
135
|
}
|
|
105
136
|
disconnect() {
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
137
|
+
++this.connectionVersion;
|
|
138
|
+
this.closeSocket();
|
|
108
139
|
}
|
|
109
140
|
on(event_name, callback_fn) {
|
|
110
141
|
this.public_listeners[event_name] = callback_fn;
|
|
@@ -113,63 +144,46 @@ export class KickPusher {
|
|
|
113
144
|
console.log(`Subscribed to Channel on Kick Pusher (${channel})`);
|
|
114
145
|
}
|
|
115
146
|
onChatSubscription(data) {
|
|
116
|
-
|
|
147
|
+
if (!isSubscription(data)) {
|
|
148
|
+
console.warn('Ignoring invalid Kick subscription payload', JSON.stringify(data));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
runSafely('kick.subscription', () => {
|
|
152
|
+
this.public_listeners.subscription?.(data);
|
|
153
|
+
});
|
|
117
154
|
}
|
|
118
155
|
onChatGifted(data) {
|
|
119
|
-
|
|
156
|
+
if (!isGifted(data)) {
|
|
157
|
+
console.warn('Ignoring invalid Kick gifted payload', JSON.stringify(data));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
runSafely('kick.gifted', () => {
|
|
161
|
+
this.public_listeners.gifted?.(data);
|
|
162
|
+
});
|
|
120
163
|
}
|
|
121
164
|
onChatMessage(data) {
|
|
122
|
-
|
|
165
|
+
if (!isChatMessage(data)) {
|
|
166
|
+
console.warn('Ignoring invalid Kick chat payload: ', JSON.stringify(data));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
runSafely('kick.raw_message', () => {
|
|
170
|
+
this.public_listeners.raw_message?.(data);
|
|
171
|
+
});
|
|
123
172
|
const text = data.content;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return;
|
|
132
|
-
body.push({
|
|
173
|
+
const emotes = [];
|
|
174
|
+
for (const match of text.matchAll(/\[emote:(\d+):[^\]]+\]/g)) {
|
|
175
|
+
const id = match[1];
|
|
176
|
+
const start = match.index;
|
|
177
|
+
if (!id || start === undefined)
|
|
178
|
+
continue;
|
|
179
|
+
emotes.push({
|
|
133
180
|
type: 'emote',
|
|
134
|
-
start_inclusive:
|
|
135
|
-
end_exclusive:
|
|
136
|
-
url: `https://files.kick.com/emotes/${
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
body.sort((a, b) => a.start_inclusive - b.start_inclusive);
|
|
140
|
-
const old_body_length = body.length;
|
|
141
|
-
if (old_body_length > 0) {
|
|
142
|
-
body.forEach((segment, index) => {
|
|
143
|
-
const previous_segment = body[index - 1];
|
|
144
|
-
const text_start_inclusive = previous_segment?.end_exclusive !== undefined ? previous_segment.end_exclusive + 1 : 0;
|
|
145
|
-
const text_end_exclusive = Math.max(0, segment.start_inclusive);
|
|
146
|
-
if (text_end_exclusive - text_start_inclusive > 0) {
|
|
147
|
-
body.push({
|
|
148
|
-
type: 'text',
|
|
149
|
-
text: text.slice(text_start_inclusive, text_end_exclusive),
|
|
150
|
-
start_inclusive: text_start_inclusive,
|
|
151
|
-
end_exclusive: text_end_exclusive,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
if (index === old_body_length - 1 && segment.end_exclusive < text.length - 1) {
|
|
155
|
-
body.push({
|
|
156
|
-
type: 'text',
|
|
157
|
-
text: text.slice(segment.end_exclusive),
|
|
158
|
-
start_inclusive: segment.end_exclusive,
|
|
159
|
-
end_exclusive: text.length,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
body.push({
|
|
166
|
-
type: 'text',
|
|
167
|
-
text,
|
|
168
|
-
start_inclusive: 0,
|
|
169
|
-
end_exclusive: text.length,
|
|
181
|
+
start_inclusive: start,
|
|
182
|
+
end_exclusive: start + match[0].length,
|
|
183
|
+
url: `https://files.kick.com/emotes/${id}/fullsize`,
|
|
170
184
|
});
|
|
171
185
|
}
|
|
172
|
-
body
|
|
186
|
+
const body = buildMessageBody(text, emotes);
|
|
173
187
|
const message = {
|
|
174
188
|
id: data.id,
|
|
175
189
|
user: {
|
|
@@ -184,12 +198,14 @@ export class KickPusher {
|
|
|
184
198
|
const badge_count = badge.count;
|
|
185
199
|
if (typeof badge_url_or_counts === 'string')
|
|
186
200
|
badge_url = badge_url_or_counts;
|
|
187
|
-
else if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
else if (badge_url_or_counts !== null &&
|
|
202
|
+
typeof badge_url_or_counts === 'object' &&
|
|
203
|
+
isFiniteNumber(badge_count)) {
|
|
204
|
+
const entry = Object.entries(badge_url_or_counts)
|
|
205
|
+
.filter(([minimum, url]) => Number.isFinite(Number(minimum)) && typeof url === 'string')
|
|
206
|
+
.sort(([a], [b]) => Number(b) - Number(a))
|
|
207
|
+
.find(([minimum]) => badge_count >= Number(minimum));
|
|
208
|
+
badge_url = entry?.[1];
|
|
193
209
|
}
|
|
194
210
|
if (!badge_url)
|
|
195
211
|
return [];
|
|
@@ -208,13 +224,76 @@ export class KickPusher {
|
|
|
208
224
|
raw_text: data.content,
|
|
209
225
|
timestamp_sent: Date.parse(data.created_at),
|
|
210
226
|
};
|
|
211
|
-
|
|
227
|
+
const metadata = data.metadata;
|
|
228
|
+
const celebration = isRecord(metadata) ? metadata['celebration'] : undefined;
|
|
229
|
+
if (data.type === 'celebration' &&
|
|
230
|
+
isRecord(celebration) &&
|
|
231
|
+
typeof celebration['id'] === 'string' &&
|
|
232
|
+
isFiniteNumber(celebration['total_months']) &&
|
|
233
|
+
celebration['total_months'] > 0 &&
|
|
234
|
+
typeof celebration['created_at'] === 'string' &&
|
|
235
|
+
Number.isFinite(Date.parse(celebration['created_at']))) {
|
|
212
236
|
message.resubscription = {
|
|
213
|
-
id:
|
|
214
|
-
months:
|
|
215
|
-
subscribed_since_timestamp:
|
|
237
|
+
id: celebration['id'],
|
|
238
|
+
months: celebration['total_months'],
|
|
239
|
+
subscribed_since_timestamp: celebration['created_at'],
|
|
216
240
|
};
|
|
217
241
|
}
|
|
218
|
-
|
|
242
|
+
runSafely('kick.message', () => {
|
|
243
|
+
this.public_listeners.message?.(message);
|
|
244
|
+
});
|
|
219
245
|
}
|
|
220
246
|
}
|
|
247
|
+
async function defaultGetChannel(channelName) {
|
|
248
|
+
const url = 'https://kick.com/api/v2/channels/' + encodeURIComponent(channelName);
|
|
249
|
+
const response = await fetch(url, {
|
|
250
|
+
headers: {
|
|
251
|
+
accept: 'application/json',
|
|
252
|
+
},
|
|
253
|
+
signal: AbortSignal.timeout(15_000),
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
throw new Error(`Kick channel lookup HTTP ${response.status}`);
|
|
257
|
+
}
|
|
258
|
+
return (await response.json());
|
|
259
|
+
}
|
|
260
|
+
function isChatMessage(value) {
|
|
261
|
+
if (!isRecord(value))
|
|
262
|
+
return false;
|
|
263
|
+
const sender = value['sender'];
|
|
264
|
+
if (typeof value['id'] !== 'string' ||
|
|
265
|
+
!isFiniteNumber(value['chatroom_id']) ||
|
|
266
|
+
typeof value['content'] !== 'string' ||
|
|
267
|
+
typeof value['created_at'] !== 'string' ||
|
|
268
|
+
!Number.isFinite(Date.parse(value['created_at'])) ||
|
|
269
|
+
!['message', 'celebration', 'reply'].includes(String(value['type'])) ||
|
|
270
|
+
!isRecord(sender) ||
|
|
271
|
+
!isFiniteNumber(sender['id']) ||
|
|
272
|
+
typeof sender['username'] !== 'string' ||
|
|
273
|
+
(sender['slug'] !== undefined && typeof sender['slug'] !== 'string')) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const identity = sender['identity'];
|
|
277
|
+
return (isRecord(identity) &&
|
|
278
|
+
typeof identity['color'] === 'string' &&
|
|
279
|
+
Array.isArray(identity['badges']) &&
|
|
280
|
+
identity['badges'].every((badge) => isRecord(badge) &&
|
|
281
|
+
typeof badge['type'] === 'string' &&
|
|
282
|
+
typeof badge['text'] === 'string' &&
|
|
283
|
+
(badge['count'] === undefined || isFiniteNumber(badge['count']))));
|
|
284
|
+
}
|
|
285
|
+
function isSubscription(value) {
|
|
286
|
+
return (isRecord(value) &&
|
|
287
|
+
isFiniteNumber(value['chatroom_id']) &&
|
|
288
|
+
typeof value['username'] === 'string' &&
|
|
289
|
+
isFiniteNumber(value['months']) &&
|
|
290
|
+
value['months'] > 0);
|
|
291
|
+
}
|
|
292
|
+
function isGifted(value) {
|
|
293
|
+
return (isRecord(value) &&
|
|
294
|
+
isFiniteNumber(value['chatroom_id']) &&
|
|
295
|
+
typeof value['gifter_username'] === 'string' &&
|
|
296
|
+
isFiniteNumber(value['gifter_total']) &&
|
|
297
|
+
Array.isArray(value['gifted_usernames']) &&
|
|
298
|
+
value['gifted_usernames'].every((name) => typeof name === 'string'));
|
|
299
|
+
}
|
package/dist/safety.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { BodyComponent } from '.';
|
|
2
|
+
export declare function runSafely(context: string, callback: () => unknown): void;
|
|
3
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
4
|
+
export declare function isFiniteNumber(value: unknown): value is number;
|
|
5
|
+
export declare function buildMessageBody(text: string, emotes: BodyComponent[]): BodyComponent[];
|
package/dist/safety.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function runSafely(context, callback) {
|
|
2
|
+
try {
|
|
3
|
+
const result = callback();
|
|
4
|
+
if (result !== null &&
|
|
5
|
+
result !== undefined &&
|
|
6
|
+
typeof result.then === 'function') {
|
|
7
|
+
void Promise.resolve(result).catch((error) => {
|
|
8
|
+
console.error(`[${context}] Async failure`, error);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
console.error(`[${context}] Failure`, error);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function isRecord(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
export function isFiniteNumber(value) {
|
|
20
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
21
|
+
}
|
|
22
|
+
export function buildMessageBody(text, emotes) {
|
|
23
|
+
const sorted = [...emotes].sort((a, b) => a.start_inclusive - b.start_inclusive);
|
|
24
|
+
const body = [];
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
for (const emote of sorted) {
|
|
27
|
+
const start = emote.start_inclusive;
|
|
28
|
+
const end = emote.end_exclusive;
|
|
29
|
+
if (!Number.isInteger(start) ||
|
|
30
|
+
!Number.isInteger(end) ||
|
|
31
|
+
start < cursor ||
|
|
32
|
+
end <= start ||
|
|
33
|
+
end > text.length) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (start > cursor) {
|
|
37
|
+
body.push({
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: text.slice(cursor, start),
|
|
40
|
+
start_inclusive: cursor,
|
|
41
|
+
end_exclusive: start,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
body.push(emote);
|
|
45
|
+
cursor = end;
|
|
46
|
+
}
|
|
47
|
+
if (cursor < text.length || body.length === 0) {
|
|
48
|
+
body.push({
|
|
49
|
+
type: 'text',
|
|
50
|
+
text: text.slice(cursor),
|
|
51
|
+
start_inclusive: cursor,
|
|
52
|
+
end_exclusive: text.length,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return body;
|
|
56
|
+
}
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { format } from 'util';
|
|
2
|
+
import { KickPusher } from './kick.js';
|
|
3
|
+
const originalError = console.error.bind(console);
|
|
4
|
+
const originalWarn = console.warn.bind(console);
|
|
5
|
+
console.warn = (...args) => {
|
|
6
|
+
originalWarn(`\x1b[33m${format(...args)}\x1b[0m`);
|
|
7
|
+
};
|
|
8
|
+
console.error = (...args) => {
|
|
9
|
+
originalError(`\x1b[31m${format(...args)}\x1b[0m`);
|
|
10
|
+
};
|
|
11
|
+
const client = new KickPusher();
|
|
12
|
+
client.on('subscription', (data) => {
|
|
13
|
+
console.log(JSON.stringify(data));
|
|
14
|
+
});
|
|
15
|
+
client.on('connection_state_changed', (data) => {
|
|
16
|
+
console.log(JSON.stringify(data));
|
|
17
|
+
});
|
|
18
|
+
client.on('gifted', (data) => {
|
|
19
|
+
console.log(JSON.stringify(data));
|
|
20
|
+
});
|
|
21
|
+
client.on('message', (data) => {
|
|
22
|
+
console.log(JSON.stringify(data));
|
|
23
|
+
});
|
|
24
|
+
client.connect({ channelName: 'drb7h' });
|
package/dist/twitch.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ type EventCallbackFunctions = {
|
|
|
10
10
|
auth_error: () => unknown;
|
|
11
11
|
};
|
|
12
12
|
type EventNames = keyof EventCallbackFunctions;
|
|
13
|
+
type SocketOptions = NonNullable<ConstructorParameters<typeof WebSocket>[2]>;
|
|
13
14
|
export declare class TwitchIRC {
|
|
14
15
|
channel_name?: string;
|
|
15
16
|
auth?: {
|
|
@@ -22,25 +23,27 @@ export declare class TwitchIRC {
|
|
|
22
23
|
latency: number;
|
|
23
24
|
private ping;
|
|
24
25
|
private public_listeners;
|
|
25
|
-
socket?: WebSocket;
|
|
26
|
-
wsCustom?: WebSocket
|
|
27
|
-
constructor(options
|
|
28
|
-
ws?: WebSocket;
|
|
26
|
+
socket?: WebSocket | undefined;
|
|
27
|
+
wsCustom?: SocketOptions['WebSocket'];
|
|
28
|
+
constructor(options?: {
|
|
29
|
+
ws?: SocketOptions['WebSocket'];
|
|
29
30
|
auth?: {
|
|
30
31
|
username: string;
|
|
31
32
|
token: string;
|
|
32
33
|
};
|
|
33
34
|
});
|
|
34
35
|
setBadges(badges: BadgeURLsBySetIDOrSetIDAndVersion): void;
|
|
35
|
-
setExternalEmotes(
|
|
36
|
+
setExternalEmotes(externalEmotes: EmoteURLsByName): void;
|
|
36
37
|
getStoredBadges(): BadgeURLsBySetIDOrSetIDAndVersion;
|
|
37
38
|
getStoredExternalEmotes(): EmoteURLsByName;
|
|
38
39
|
authenticate(username: string, token: string): void;
|
|
40
|
+
private clearPing;
|
|
39
41
|
connect(channel?: {
|
|
40
42
|
channelName?: string;
|
|
41
43
|
}): void;
|
|
42
44
|
send(message: string, replyParentMessageId?: string): void;
|
|
43
45
|
disconnect(): void;
|
|
46
|
+
private restartConnection;
|
|
44
47
|
on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
|
|
45
48
|
isConnected(): this is {
|
|
46
49
|
socket: {
|
|
@@ -48,43 +51,22 @@ export declare class TwitchIRC {
|
|
|
48
51
|
} & WebSocket;
|
|
49
52
|
};
|
|
50
53
|
private onOpen;
|
|
51
|
-
private onClose;
|
|
52
54
|
private sendPing;
|
|
53
55
|
private sendIRC;
|
|
54
56
|
private onMessage;
|
|
57
|
+
private handleIRCMessage;
|
|
55
58
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}[CommandType];
|
|
65
|
-
type RAW_TAGS = typeof RAW_TAGS;
|
|
66
|
-
type CommandType = keyof RAW_TAGS;
|
|
67
|
-
type SpecificRawTags<Command extends CommandType> = Record<RAW_TAGS[Command][number], string | undefined>;
|
|
59
|
+
export declare function parseIRCLine(line: string): IRC_Message | undefined;
|
|
60
|
+
export type IRC_Message = {
|
|
61
|
+
channel: string;
|
|
62
|
+
command: string;
|
|
63
|
+
params: string[];
|
|
64
|
+
source: RawSource | undefined;
|
|
65
|
+
tags: Record<string, string | undefined> | undefined;
|
|
66
|
+
};
|
|
68
67
|
type RawSource = {
|
|
69
68
|
host: string;
|
|
70
69
|
nick?: string | undefined;
|
|
71
70
|
user?: string | undefined;
|
|
72
71
|
};
|
|
73
|
-
declare const RAW_TAGS: {
|
|
74
|
-
readonly CLEARCHAT: readonly ["ban-duration", "room-id", "target-user-id", "tmi-sent-ts"];
|
|
75
|
-
readonly CLEARMSG: readonly ["login", "room-id", "target-msg-id", "tmi-sent-ts"];
|
|
76
|
-
readonly GLOBALUSERSTATE: readonly ["badge-info", "badges", "color", "display-name", "emote-sets", "turbo", "user-id", "user-type"];
|
|
77
|
-
readonly HOSTTARGET: readonly [];
|
|
78
|
-
readonly NOTICE: readonly ["msg-id", "target-user-id"];
|
|
79
|
-
readonly PART: readonly [];
|
|
80
|
-
readonly PING: readonly [];
|
|
81
|
-
readonly PONG: readonly [];
|
|
82
|
-
readonly '001': readonly [];
|
|
83
|
-
readonly PRIVMSG: readonly ["badge-info", "badges", "bits", "color", "display-name", "emotes", "emote-only", "id", "mod", "custom-reward-id", "reply-thread-parent-display-name", "reply-thread-parent-user-id", "pinned-chat-paid-amount", "pinned-chat-paid-currency", "pinned-chat-paid-exponent", "pinned-chat-paid-level", "pinned-chat-paid-is-system-message", "reply-parent-msg-id", "reply-parent-user-id", "reply-parent-user-login", "reply-parent-display-name", "reply-parent-msg-body", "reply-thread-parent-msg-id", "reply-thread-parent-user-login", "room-id", "subscriber", "tmi-sent-ts", "turbo", "user-id", "user-type", "vip", "client-nonce", "first-msg", "flags", "returning-chatter"];
|
|
84
|
-
readonly RECONNECT: readonly [];
|
|
85
|
-
readonly ROOMSTATE: readonly ["emote-only", "followers-only", "r9k", "room-id", "slow", "subs-only"];
|
|
86
|
-
readonly USERNOTICE: readonly ["badge-info", "badges", "color", "display-name", "emotes", "id", "login", "mod", "msg-id", "room-id", "subscriber", "system-msg", "tmi-sent-ts", "turbo", "user-id", "user-type", "vip", "flags", "msg-param-cumulative-months", "msg-param-displayName", "msg-param-login", "msg-param-multimonth-duration", "msg-param-multimonth-tenure", "msg-param-was-gifted=false", "msg-param-months", "msg-param-promo-gift-total", "msg-param-promo-name", "msg-param-recipient-display-name", "msg-param-recipient-id", "msg-param-recipient-user-name", "msg-param-sender-login", "msg-param-sender-name", "msg-param-should-share-streak", "msg-param-streak-months", "msg-param-sub-plan", "msg-param-sub-plan-name", "msg-param-viewerCount", "msg-param-ritual-name", "msg-param-threshold", "msg-param-gift-months", "msg-param-was-gifted", "msg-param-community-gift-id", "msg-param-mass-gift-count", "msg-param-origin-id"];
|
|
87
|
-
readonly USERSTATE: readonly ["badge-info", "badges", "color", "display-name", "emote-sets", "id", "mod", "subscriber", "turbo", "user-type"];
|
|
88
|
-
readonly WHISPER: readonly ["badges", "color", "display-name", "emotes", "message-id", "thread-id", "turbo", "user-id", "user-type"];
|
|
89
|
-
};
|
|
90
72
|
export {};
|