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,425 +0,0 @@
1
- import { WebSocket } from 'partysocket';
2
- const ANONYMOUS_IRC_PASS = 'SCHMOOPIIE';
3
- const ANONYMOUS_IRC_LOGIN = 'justinfan1234';
4
- const PING_INTERVAL_MS = 30_000;
5
- const PING_TIMEOUT_MS = 10_000;
6
- export class TwitchIRC {
7
- channel_name;
8
- assets = {
9
- external_emotes: {},
10
- badges: {},
11
- };
12
- latency = 0;
13
- ping = {};
14
- public_listeners = {};
15
- socket;
16
- ws;
17
- constructor(ws) {
18
- this.ws = ws;
19
- }
20
- setBadges(badges) {
21
- this.assets.badges = { ...badges, ...this.assets.badges };
22
- }
23
- setExternalEmotes(external_emotes) {
24
- this.assets.external_emotes = { ...external_emotes, ...this.assets.external_emotes };
25
- }
26
- getStoredBadges() {
27
- return this.assets.badges;
28
- }
29
- getStoredExternalEmotes() {
30
- return this.assets.external_emotes;
31
- }
32
- connect(channel) {
33
- if (channel?.channelName)
34
- this.channel_name = channel.channelName;
35
- if (!this.channel_name)
36
- return console.error('channel_name not specified');
37
- console.log(`connecting to ${this.channel_name}...`);
38
- this.socket?.close();
39
- this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
40
- WebSocket: this.ws,
41
- });
42
- this.socket.onopen = () => this.onOpen();
43
- this.socket.onclose = () => this.onClose();
44
- this.socket.onmessage = (event) => this.onMessage(event);
45
- }
46
- disconnect() {
47
- this.socket?.close();
48
- }
49
- on(event_name, callback_fn) {
50
- this.public_listeners[event_name] = callback_fn;
51
- }
52
- isConnected() {
53
- return !!this.socket && this.socket.readyState === WebSocket.OPEN;
54
- }
55
- onOpen() {
56
- this.send('CAP REQ :twitch.tv/commands twitch.tv/tags');
57
- this.send(`PASS ${ANONYMOUS_IRC_PASS}`);
58
- this.send(`NICK ${ANONYMOUS_IRC_LOGIN}`);
59
- this.send(`JOIN #${this.channel_name}`);
60
- console.log(`Connected to Twitch IRC as Anonymous (${this.channel_name})`);
61
- if (this.ping.interval)
62
- clearInterval(this.ping.interval);
63
- this.sendPing();
64
- this.ping.interval = setInterval(() => this.sendPing(), PING_INTERVAL_MS);
65
- }
66
- onClose() {
67
- clearInterval(this.ping.interval);
68
- clearTimeout(this.ping.timeout);
69
- }
70
- sendPing() {
71
- this.send('PING');
72
- this.ping.lastSentTimestamp = Date.now();
73
- if (this.ping.timeout)
74
- clearTimeout(this.ping.timeout);
75
- this.ping.timeout = setTimeout(() => {
76
- console.error('PING Timeout, reconnecting...');
77
- this.connect();
78
- }, PING_TIMEOUT_MS);
79
- }
80
- send(irc_message) {
81
- if (!this.isConnected()) {
82
- throw new Error('Not connected');
83
- }
84
- this.socket?.send(irc_message);
85
- }
86
- onMessage(event) {
87
- if (!event.data)
88
- return;
89
- const lines = event.data.trim().split('\r\n');
90
- const messages = lines.map(parseIRCLine);
91
- messages.forEach((message) => {
92
- this.public_listeners.raw_message?.(message);
93
- switch (message.command) {
94
- case 'PONG': {
95
- if (!this.ping.lastSentTimestamp)
96
- return console.error('got PONG without sending PING');
97
- clearInterval(this.ping.timeout);
98
- this.latency = Date.now() - this.ping.lastSentTimestamp;
99
- this.ping.lastSentTimestamp = undefined;
100
- break;
101
- }
102
- case 'PING': {
103
- this.send('PONG');
104
- break;
105
- }
106
- case 'CLEARCHAT': {
107
- const { channel, tags } = message;
108
- if (!tags)
109
- return;
110
- this.public_listeners.clear_messages?.({
111
- channel: {
112
- name: channel,
113
- room_id: tags['room-id'] ?? 'unknown',
114
- },
115
- timestamp_sent: Number(tags['tmi-sent-ts']),
116
- timeout_duration_seconds: tags['ban-duration']
117
- ? Number(tags['ban-duration'])
118
- : undefined,
119
- });
120
- break;
121
- }
122
- case 'PRIVMSG': {
123
- const { channel, params, tags } = message;
124
- if (!tags || !tags['user-id'] || !tags['id'] || !tags['room-id'])
125
- return;
126
- const text = params[0];
127
- if (!text)
128
- return;
129
- const body = [];
130
- tags.emotes?.split('/').forEach((raw_emote_string) => {
131
- const [emote_id, raw_emote_positions_string] = raw_emote_string.split(':');
132
- if (!emote_id || !raw_emote_positions_string)
133
- return;
134
- const raw_emote_positions = raw_emote_positions_string.split(',');
135
- raw_emote_positions.forEach((raw_emote_position) => {
136
- const [emote_start, emote_end] = raw_emote_position.split('-');
137
- if (!emote_start || !emote_end)
138
- return;
139
- body.push({
140
- type: 'emote',
141
- start_inclusive: +emote_start,
142
- end_exclusive: +emote_end + 1,
143
- url: `https://static-cdn.jtvnw.net/emoticons/v2/${emote_id}/default/dark/1.0`,
144
- });
145
- });
146
- });
147
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
148
- const old_body_length = body.length;
149
- if (old_body_length > 0) {
150
- body.forEach((segment, index) => {
151
- const previous_segment = body[index - 1];
152
- const text_start_inclusive = previous_segment?.end_exclusive !== undefined
153
- ? previous_segment.end_exclusive + 1
154
- : 0;
155
- const text_end_exclusive = Math.max(0, segment.start_inclusive);
156
- if (text_end_exclusive - text_start_inclusive > 0) {
157
- body.push({
158
- type: 'text',
159
- text: text.slice(text_start_inclusive, text_end_exclusive),
160
- start_inclusive: text_start_inclusive,
161
- end_exclusive: text_end_exclusive,
162
- });
163
- }
164
- if (index === old_body_length - 1 && segment.end_exclusive < text.length - 1) {
165
- body.push({
166
- type: 'text',
167
- text: text.slice(segment.end_exclusive),
168
- start_inclusive: segment.end_exclusive,
169
- end_exclusive: text.length,
170
- });
171
- }
172
- });
173
- }
174
- else {
175
- body.push({
176
- type: 'text',
177
- text,
178
- start_inclusive: 0,
179
- end_exclusive: text.length,
180
- });
181
- }
182
- body.sort((a, b) => a.start_inclusive - b.start_inclusive);
183
- const badge_info = {};
184
- tags['badge-info']?.split(',').forEach((badge) => {
185
- const [set_id, info] = badge.split('/');
186
- if (!set_id || !info)
187
- return;
188
- badge_info[set_id] = info;
189
- });
190
- this.public_listeners.message?.({
191
- body: body,
192
- channel: {
193
- room_id: tags['room-id'],
194
- name: channel,
195
- },
196
- id: tags.id,
197
- raw_text: text,
198
- timestamp_sent: Number(tags['tmi-sent-ts']),
199
- user: {
200
- badges: tags.badges?.split(',').flatMap((badge) => {
201
- const [set_id, version] = badge.split('/');
202
- if (!set_id || !version)
203
- return [];
204
- const storedBadge = this.assets.badges[set_id];
205
- if (!storedBadge)
206
- return [];
207
- const url = typeof storedBadge === 'object' ? storedBadge[version] : storedBadge;
208
- if (!url)
209
- return [];
210
- return {
211
- info: badge_info[set_id],
212
- set_id,
213
- url,
214
- };
215
- }) ?? [],
216
- color: tags.color ?? '#FFFFFF',
217
- id: tags['user-id'],
218
- username: (tags['display-name'] ?? 'Unknown').toLowerCase(),
219
- display_name: tags['display-name'] ?? 'Unknown',
220
- roles: {},
221
- },
222
- });
223
- break;
224
- }
225
- }
226
- });
227
- }
228
- }
229
- function parseIRCLine(line) {
230
- const components = line.split(' ');
231
- let componentIndex = 0;
232
- let raw_tags_component;
233
- let source;
234
- if (components[componentIndex].startsWith('@')) {
235
- raw_tags_component = components[componentIndex].slice(1);
236
- componentIndex++;
237
- }
238
- if (components[componentIndex].startsWith(':')) {
239
- source = parseSource(components[componentIndex].slice(1));
240
- componentIndex++;
241
- }
242
- const command = components[componentIndex];
243
- componentIndex++;
244
- let channel = '';
245
- if (components[componentIndex].startsWith('#'))
246
- channel = components[componentIndex].slice(1);
247
- componentIndex++;
248
- const params = [];
249
- while (components[componentIndex] !== undefined) {
250
- const param = components[componentIndex];
251
- if (param.startsWith(':')) {
252
- params.push(components.slice(componentIndex).join(' ').slice(1));
253
- componentIndex = -1;
254
- }
255
- else {
256
- params.push(param);
257
- componentIndex++;
258
- }
259
- }
260
- if (params[0])
261
- params[0] = String.raw `${params[0]}`.replaceAll(/.+ACTION (.*).+/g, (_original, group) => group);
262
- const tags = raw_tags_component ? parseTags(raw_tags_component, command) : undefined;
263
- return {
264
- channel,
265
- command,
266
- params,
267
- source,
268
- tags,
269
- };
270
- }
271
- function parseTags(component, command) {
272
- const tags = {};
273
- component.split(';').forEach((raw_tag) => {
274
- const [key, value] = raw_tag.split('=');
275
- if (RAW_TAGS[command].findIndex((el) => el === key) === -1)
276
- console.warn(`[${command}] Unknown Tag: ${raw_tag}`);
277
- tags[key] = value;
278
- });
279
- return tags;
280
- }
281
- function parseSource(component) {
282
- let user = undefined;
283
- let host = component;
284
- let nick = undefined;
285
- if (component.includes('!'))
286
- [nick, host] = component.split('!');
287
- if (host?.includes('@'))
288
- [user, host] = host.split('@');
289
- return {
290
- host: host ?? 'unknown',
291
- nick,
292
- user,
293
- };
294
- }
295
- const RAW_TAGS = {
296
- CLEARCHAT: ['ban-duration', 'room-id', 'target-user-id', 'tmi-sent-ts'],
297
- CLEARMSG: ['login', 'room-id', 'target-msg-id', 'tmi-sent-ts'],
298
- GLOBALUSERSTATE: [
299
- 'badge-info',
300
- 'badges',
301
- 'color',
302
- 'display-name',
303
- 'emote-sets',
304
- 'turbo',
305
- 'user-id',
306
- 'user-type',
307
- ],
308
- HOSTTARGET: [],
309
- NOTICE: ['msg-id', 'target-user-id'],
310
- PART: [],
311
- PING: [],
312
- PONG: [],
313
- PRIVMSG: [
314
- 'badge-info',
315
- 'badges',
316
- 'bits',
317
- 'color',
318
- 'display-name',
319
- 'emotes',
320
- 'emote-only',
321
- 'id',
322
- 'mod',
323
- 'custom-reward-id',
324
- 'reply-thread-parent-display-name',
325
- 'reply-thread-parent-user-id',
326
- 'pinned-chat-paid-amount',
327
- 'pinned-chat-paid-currency',
328
- 'pinned-chat-paid-exponent',
329
- 'pinned-chat-paid-level',
330
- 'pinned-chat-paid-is-system-message',
331
- 'reply-parent-msg-id',
332
- 'reply-parent-user-id',
333
- 'reply-parent-user-login',
334
- 'reply-parent-display-name',
335
- 'reply-parent-msg-body',
336
- 'reply-thread-parent-msg-id',
337
- 'reply-thread-parent-user-login',
338
- 'room-id',
339
- 'subscriber',
340
- 'tmi-sent-ts',
341
- 'turbo',
342
- 'user-id',
343
- 'user-type',
344
- 'vip',
345
- ...[
346
- 'client-nonce',
347
- 'first-msg',
348
- 'flags',
349
- 'returning-chatter',
350
- ],
351
- ],
352
- RECONNECT: [],
353
- ROOMSTATE: ['emote-only', 'followers-only', 'r9k', 'room-id', 'slow', 'subs-only'],
354
- USERNOTICE: [
355
- 'badge-info',
356
- 'badges',
357
- 'color',
358
- 'display-name',
359
- 'emotes',
360
- 'id',
361
- 'login',
362
- 'mod',
363
- 'msg-id',
364
- 'room-id',
365
- 'subscriber',
366
- 'system-msg',
367
- 'tmi-sent-ts',
368
- 'turbo',
369
- 'user-id',
370
- 'user-type',
371
- 'vip',
372
- 'flags',
373
- ...[
374
- 'msg-param-cumulative-months',
375
- 'msg-param-displayName',
376
- 'msg-param-login',
377
- 'msg-param-multimonth-duration',
378
- 'msg-param-multimonth-tenure',
379
- 'msg-param-was-gifted=false',
380
- 'msg-param-months',
381
- 'msg-param-promo-gift-total',
382
- 'msg-param-promo-name',
383
- 'msg-param-recipient-display-name',
384
- 'msg-param-recipient-id',
385
- 'msg-param-recipient-user-name',
386
- 'msg-param-sender-login',
387
- 'msg-param-sender-name',
388
- 'msg-param-should-share-streak',
389
- 'msg-param-streak-months',
390
- 'msg-param-sub-plan',
391
- 'msg-param-sub-plan-name',
392
- 'msg-param-viewerCount',
393
- 'msg-param-ritual-name',
394
- 'msg-param-threshold',
395
- 'msg-param-gift-months',
396
- 'msg-param-was-gifted',
397
- 'msg-param-community-gift-id',
398
- 'msg-param-mass-gift-count',
399
- 'msg-param-origin-id',
400
- ],
401
- ],
402
- USERSTATE: [
403
- 'badge-info',
404
- 'badges',
405
- 'color',
406
- 'display-name',
407
- 'emote-sets',
408
- 'id',
409
- 'mod',
410
- 'subscriber',
411
- 'turbo',
412
- 'user-type',
413
- ],
414
- WHISPER: [
415
- 'badges',
416
- 'color',
417
- 'display-name',
418
- 'emotes',
419
- 'message-id',
420
- 'thread-id',
421
- 'turbo',
422
- 'user-id',
423
- 'user-type',
424
- ],
425
- };
package/index.js DELETED
@@ -1,9 +0,0 @@
1
- import { TwitchIRC } from './dist/twitch.js';
2
- import WS from 'ws';
3
-
4
- const twitch_chat = new TwitchIRC();
5
- twitch_chat.ws = WS;
6
-
7
- twitch_chat.connect({
8
- channelName: '3b006',
9
- });
@@ -1,173 +0,0 @@
1
- export * from './kick.js';
2
- export * from './twitch.js';
3
-
4
- export type Message = {
5
- body: BodyComponent[];
6
- channel: {
7
- room_id: string;
8
- name: string;
9
- };
10
- id: string;
11
- raw_text: string;
12
- timestamp_sent: number;
13
- resubscription?: {
14
- id: string;
15
- months: number;
16
- subscribed_since_timestamp: string;
17
- };
18
- user: {
19
- badges: Badge[];
20
- color: string;
21
- id: string;
22
- username: string;
23
- display_name: string;
24
- roles: {
25
- [role in 'vip' | 'moderator' | 'turbo' | 'admin' | 'global_moderator' | 'staff']?: boolean;
26
- };
27
- };
28
- };
29
-
30
- export type ClearMessages = {
31
- channel: {
32
- room_id: string;
33
- name: string;
34
- };
35
- user?: {
36
- id: string;
37
- username: string;
38
- };
39
- timeout_duration_seconds?: number | undefined;
40
- timestamp_sent: number;
41
- };
42
-
43
- export type DeleteMessage = {
44
- channel: {
45
- room_id?: string;
46
- name: string;
47
- };
48
- user?: {
49
- username: string;
50
- };
51
- id: string;
52
- raw_text: string;
53
- timestamp_sent: number;
54
- };
55
-
56
- type EventData = {
57
- subscription: {
58
- months: number;
59
- streak?: number;
60
- subscription_plan: {
61
- type: 'prime' | 1000 | 2000 | 3000;
62
- name: string;
63
- };
64
- gift_upgrade?: {
65
- gift_total: number;
66
- promo_name: string;
67
- sender: {
68
- username: string;
69
- display_name: string;
70
- };
71
- };
72
- };
73
- gift: {
74
- months: number;
75
- recipient: {
76
- id: string;
77
- username: string;
78
- display_name: string;
79
- };
80
- subscription_plan: {
81
- type: 'prime' | 1000 | 2000 | 3000;
82
- name: string;
83
- };
84
- };
85
- raid: {
86
- sender: {
87
- username: string;
88
- display_name: string;
89
- viewer_count: number;
90
- };
91
- };
92
- };
93
-
94
- export type Event = {
95
- [E in keyof EventData]: {
96
- type: E;
97
- body: BodyComponent[];
98
- channel: {
99
- room_id: string;
100
- name: string;
101
- };
102
- id: string;
103
- raw_text: string;
104
- system_message: string;
105
- timestamp_sent: number;
106
- user: {
107
- badges: Badge[];
108
- color: string;
109
- id: string;
110
- username: string;
111
- display_name: string;
112
- roles: {
113
- [role in
114
- | 'moderator'
115
- | 'subscriber'
116
- | 'turbo'
117
- | 'admin'
118
- | 'global_moderator'
119
- | 'staff']?: boolean;
120
- };
121
- };
122
- data: EventData[E];
123
- };
124
- }[keyof EventData];
125
-
126
- export type BodyComponentEmote = {
127
- end_exclusive: number;
128
- start_inclusive: number;
129
- type: 'emote';
130
- url: string;
131
- };
132
-
133
- export type BodyComponent =
134
- | {
135
- end_exclusive: number;
136
- label: string;
137
- start_inclusive: number;
138
- type: 'link';
139
- url: string;
140
- }
141
- | {
142
- end_exclusive: number;
143
- start_inclusive: number;
144
- text: string;
145
- type: 'text';
146
- }
147
- | BodyComponentEmote;
148
-
149
- type Badge = {
150
- info?: string | undefined;
151
- set_id: string;
152
- url: string;
153
- };
154
-
155
- export type BadgeURLsByNameOrCount = {
156
- [badge_name: string]:
157
- | string
158
- | {
159
- [badge_count: number]: string;
160
- };
161
- };
162
-
163
- export type BadgeURLsBySetIDOrSetIDAndVersion = {
164
- [set_id: string]:
165
- | {
166
- [version: string]: string;
167
- }
168
- | string;
169
- };
170
-
171
- export type EmoteURLsByName = {
172
- [emote_name: string]: string;
173
- };