multichat-ts 0.0.1 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # multichat-ts
2
+
3
+ Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)
4
+
5
+ ## Usage
6
+
7
+ ```sh
8
+ pnpm install multichat-ts # or (npm, bun, deno, etc) install
9
+ ```
10
+
11
+ ```ts
12
+ import { TwitchIRC, KickPusher } from 'multichat-ts';
13
+
14
+ const twitch_chat = new TwitchIRC();
15
+ const kick_chat = new KickPusher();
16
+
17
+ twitch_chat.connect({
18
+ channelName: 'piratesoftware',
19
+ });
20
+
21
+ kick_chat.connect({
22
+ channelName: 'xqc',
23
+ });
24
+
25
+ // set custom badge assets or external emotes (ffz, bttv, 7tv, etc)
26
+ twitch_chat.assets.external_emotes = {
27
+ OMEGALUL: 'https://cdn.frankerfacez.com/emoticon/128054/1',
28
+ };
29
+
30
+ twitch_chat.on('message', (message) => {
31
+ console.log('new twitch message', message.raw_text, message);
32
+ });
33
+
34
+ kick_chat.on('message', (message) => {
35
+ console.log('new twitch message', message.raw_text, message);
36
+ });
37
+ ```
38
+
39
+ ### Example usage with Svelte 5
40
+
41
+ ```svelte
42
+ <script lang="ts">
43
+ import { TwitchIRC, type Message } from 'multichat-ts';
44
+
45
+ let messages: Message[] = $state([]);
46
+
47
+ const twitch_chat = new TwitchIRC();
48
+ $effect(() => {
49
+ twitch_chat.connect({
50
+ channelName: 'piratesoftware'
51
+ });
52
+
53
+ return () => twitch_chat.disconnect();
54
+ });
55
+
56
+ twitch_chat.on('message', (message) => {
57
+ messages.push(message);
58
+ });
59
+
60
+ twitch_chat.on('clear_messages', (data) => {
61
+ if (data.user !== undefined) {
62
+ messages.filter((message) => message.user.id !== data.user!.id);
63
+ } else {
64
+ messages.filter((message) => message.channel.room_id !== data.channel.room_id);
65
+ }
66
+ });
67
+
68
+ twitch_chat.on('delete_message', (message_data) => {
69
+ messages.filter((message) => message.id !== message_data.id);
70
+ });
71
+ </script>
72
+
73
+ <ul>
74
+ {#each messages as message}
75
+ <li>{message.user.display_name}: {message.raw_text}</li>
76
+ {/each}
77
+ </ul>
78
+ ```
79
+
80
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './kick';
2
- export * from './twitch';
1
+ export * from './kick.js';
2
+ export * from './twitch.js';
3
3
  export type Message = {
4
4
  body: BodyComponent[];
5
5
  channel: {
@@ -134,10 +134,9 @@ export type BadgeURLsByNameOrCount = {
134
134
  [badge_count: number]: string;
135
135
  };
136
136
  };
137
- export type BadgeURLsBySetIDAndVersionID = {
138
- [badge_set_id: string]: {
139
- [badge_version_id: string]: string;
140
- };
137
+ type StringOrAny = '*' | (string & Record<never, never>);
138
+ export type BadgeURLsBySetIDAndVersionIDOrAny = {
139
+ [badge_set_id: string]: Record<StringOrAny, string>;
141
140
  };
142
141
  export type EmoteURLsByName = {
143
142
  [emote_name: string]: string;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from './kick';
2
- export * from './twitch';
1
+ export * from './kick.js';
2
+ export * from './twitch.js';
package/dist/kick.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import Pusher from 'pusher-js';
2
- import { type BadgeURLsByNameOrCount, type EmoteURLsByName, type Message } from './index';
2
+ import { type BadgeURLsByNameOrCount, type EmoteURLsByName, type Message } from './index.js';
3
3
  type EventCallbackFunctions = {
4
4
  message: (data: Message) => unknown;
5
5
  };
package/dist/kick.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import Pusher from 'pusher-js';
2
- import {} from './index';
3
2
  const DEFAULT_KICK_PUSHER_KEY = '32cbd69e4b950bf97679';
4
3
  export class KickPusher {
5
4
  kick_pusher_key = DEFAULT_KICK_PUSHER_KEY;
package/dist/twitch.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WebSocket } from 'partysocket';
2
- import { type ClearMessages, type DeleteMessage, type Message, type Event, type EmoteURLsByName, type BadgeURLsBySetIDAndVersionID } from './index';
2
+ import { type ClearMessages, type DeleteMessage, type Message, type Event, type EmoteURLsByName, type BadgeURLsBySetIDAndVersionIDOrAny } from './index.js';
3
3
  type EventCallbackFunctions = {
4
4
  message: (data: Message) => unknown;
5
5
  clear_messages: (data: ClearMessages) => unknown;
@@ -11,12 +11,14 @@ export declare class TwitchIRC {
11
11
  channel_name?: string;
12
12
  assets: {
13
13
  external_emotes: EmoteURLsByName;
14
- badges: BadgeURLsBySetIDAndVersionID;
14
+ badges: BadgeURLsBySetIDAndVersionIDOrAny;
15
15
  };
16
16
  latency: number;
17
17
  private ping;
18
18
  private public_listeners;
19
19
  socket?: WebSocket;
20
+ ws?: WebSocket | undefined;
21
+ constructor(ws?: WebSocket);
20
22
  connect(channel?: {
21
23
  channelName?: string;
22
24
  }): void;
package/dist/twitch.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { WebSocket } from 'partysocket';
2
- import {} from './index';
3
2
  const ANONYMOUS_IRC_PASS = 'SCHMOOPIIE';
4
3
  const ANONYMOUS_IRC_LOGIN = 'justinfan1234';
5
4
  const PING_INTERVAL_MS = 30_000;
@@ -14,6 +13,10 @@ export class TwitchIRC {
14
13
  ping = {};
15
14
  public_listeners = {};
16
15
  socket;
16
+ ws;
17
+ constructor(ws) {
18
+ this.ws = ws;
19
+ }
17
20
  connect(channel) {
18
21
  if (channel?.channelName)
19
22
  this.channel_name = channel.channelName;
@@ -21,11 +24,12 @@ export class TwitchIRC {
21
24
  return console.error('channel_name not specified');
22
25
  console.log(`connecting to ${this.channel_name}...`);
23
26
  this.socket?.close();
24
- this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv');
27
+ this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
28
+ WebSocket: this.ws,
29
+ });
25
30
  this.socket.onopen = () => this.onOpen();
26
31
  this.socket.onclose = () => this.onClose();
27
32
  this.socket.onmessage = (event) => this.onMessage(event);
28
- console.log(parseIRCLine(`@badge-info=;badges=turbo/1;color=#9ACD32;display-name=TestChannel;emotes=;id=3d830f12-795c-447d-af3c-ea05e40fbddb;login=testchannel;mod=0;msg-id=raid;msg-param-displayName=TestChannel;msg-param-login=testchannel;msg-param-viewerCount=15;room-id=33332222;subscriber=0;system-msg=15\sraiders\sfrom\sTestChannel\shave\sjoined\n!;tmi-sent-ts=1507246572675;turbo=1;user-id=123456;user-type= :tmi.twitch.tv USERNOTICE #othertestchannel`));
29
33
  }
30
34
  disconnect() {
31
35
  this.socket?.close();
@@ -184,7 +188,7 @@ export class TwitchIRC {
184
188
  const [set_id, version] = badge.split('/');
185
189
  if (!set_id || !version)
186
190
  return [];
187
- const url = this.assets.badges[set_id]?.[version];
191
+ const url = this.assets.badges[set_id]?.[version] ?? this.assets.badges[set_id]?.['*'];
188
192
  if (!url)
189
193
  return [];
190
194
  return {
@@ -195,7 +199,7 @@ export class TwitchIRC {
195
199
  }) ?? [],
196
200
  color: tags.color ?? '#FFFFFF',
197
201
  id: tags['user-id'],
198
- username: 'IDK',
202
+ username: (tags['display-name'] ?? 'Unknown').toLowerCase(),
199
203
  display_name: tags['display-name'] ?? 'Unknown',
200
204
  roles: {},
201
205
  },
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "multichat-ts",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
- "description": "Receive real-time events for chat-related messages and events on multiple platforms (Twitch, Kick)",
5
+ "description": "Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/0Abdullah/multichat-ts.git"
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './kick';
2
- export * from './twitch';
1
+ export * from './kick.js';
2
+ export * from './twitch.js';
3
3
 
4
4
  export type Message = {
5
5
  body: BodyComponent[];
@@ -155,10 +155,10 @@ export type BadgeURLsByNameOrCount = {
155
155
  };
156
156
  };
157
157
 
158
- export type BadgeURLsBySetIDAndVersionID = {
159
- [badge_set_id: string]: {
160
- [badge_version_id: string]: string;
161
- };
158
+ type StringOrAny = '*' | (string & Record<never, never>);
159
+
160
+ export type BadgeURLsBySetIDAndVersionIDOrAny = {
161
+ [badge_set_id: string]: Record<StringOrAny, string>;
162
162
  };
163
163
 
164
164
  export type EmoteURLsByName = {
package/src/kick.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  type BodyComponent,
6
6
  type EmoteURLsByName,
7
7
  type Message,
8
- } from './index';
8
+ } from './index.js';
9
9
 
10
10
  type EventCallbackFunctions = {
11
11
  message: (data: Message) => unknown;
package/src/twitch.ts CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  type Message,
8
8
  type Event,
9
9
  type EmoteURLsByName,
10
- type BadgeURLsBySetIDAndVersionID,
11
- } from './index';
10
+ type BadgeURLsBySetIDAndVersionIDOrAny,
11
+ } from './index.js';
12
12
 
13
13
  const ANONYMOUS_IRC_PASS = 'SCHMOOPIIE';
14
14
  const ANONYMOUS_IRC_LOGIN = 'justinfan1234';
@@ -28,7 +28,7 @@ export class TwitchIRC {
28
28
  public channel_name?: string;
29
29
  public assets: {
30
30
  external_emotes: EmoteURLsByName;
31
- badges: BadgeURLsBySetIDAndVersionID;
31
+ badges: BadgeURLsBySetIDAndVersionIDOrAny;
32
32
  } = {
33
33
  external_emotes: {},
34
34
  badges: {},
@@ -44,6 +44,11 @@ export class TwitchIRC {
44
44
  private public_listeners: Partial<EventCallbackFunctions> = {};
45
45
 
46
46
  public socket?: WebSocket;
47
+ public ws?: WebSocket | undefined;
48
+
49
+ constructor(ws?: WebSocket) {
50
+ this.ws = ws;
51
+ }
47
52
 
48
53
  public connect(channel?: { channelName?: string }) {
49
54
  if (channel?.channelName) this.channel_name = channel.channelName;
@@ -53,17 +58,12 @@ export class TwitchIRC {
53
58
 
54
59
  this.socket?.close();
55
60
 
56
- this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv');
61
+ this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
62
+ WebSocket: this.ws,
63
+ });
57
64
  this.socket.onopen = () => this.onOpen();
58
65
  this.socket.onclose = () => this.onClose();
59
66
  this.socket.onmessage = (event) => this.onMessage(event);
60
-
61
- console.log(
62
- parseIRCLine(
63
- // eslint-disable-next-line no-useless-escape
64
- `@badge-info=;badges=turbo/1;color=#9ACD32;display-name=TestChannel;emotes=;id=3d830f12-795c-447d-af3c-ea05e40fbddb;login=testchannel;mod=0;msg-id=raid;msg-param-displayName=TestChannel;msg-param-login=testchannel;msg-param-viewerCount=15;room-id=33332222;subscriber=0;system-msg=15\sraiders\sfrom\sTestChannel\shave\sjoined\n!;tmi-sent-ts=1507246572675;turbo=1;user-id=123456;user-type= :tmi.twitch.tv USERNOTICE #othertestchannel`,
65
- ),
66
- );
67
67
  }
68
68
 
69
69
  public disconnect() {
@@ -244,7 +244,8 @@ export class TwitchIRC {
244
244
  const [set_id, version] = badge.split('/');
245
245
  if (!set_id || !version) return [];
246
246
 
247
- const url = this.assets.badges[set_id]?.[version];
247
+ const url =
248
+ this.assets.badges[set_id]?.[version] ?? this.assets.badges[set_id]?.['*'];
248
249
  if (!url) return [];
249
250
 
250
251
  return {
@@ -255,7 +256,7 @@ export class TwitchIRC {
255
256
  }) ?? [],
256
257
  color: tags.color ?? '#FFFFFF',
257
258
  id: tags['user-id'],
258
- username: 'IDK',
259
+ username: (tags['display-name'] ?? 'Unknown').toLowerCase(),
259
260
  display_name: tags['display-name'] ?? 'Unknown',
260
261
  roles: {},
261
262
  },
package/tsconfig.json CHANGED
@@ -1,45 +1,45 @@
1
1
  {
2
- "compilerOptions": {
3
- /* Visit https://aka.ms/tsconfig to read more about this file */
2
+ "compilerOptions": {
3
+ /* Visit https://aka.ms/tsconfig to read more about this file */
4
4
 
5
- /* Language and Environment */
6
- "target": "ESNext",
5
+ /* Language and Environment */
6
+ "target": "ESNext",
7
7
 
8
- /* Modules */
9
- "module": "ESNext",
10
- "moduleResolution": "Bundler",
8
+ /* Modules */
9
+ "module": "ESNext",
10
+ "moduleResolution": "Bundler",
11
11
 
12
- /* JavaScript Support */
13
- "checkJs": true,
12
+ /* JavaScript Support */
13
+ "checkJs": true,
14
14
 
15
- /* Emit */
16
- "declaration": true,
17
- "outDir": "dist",
18
- "removeComments": true,
15
+ /* Emit */
16
+ "declaration": true,
17
+ "outDir": "dist",
18
+ "removeComments": true,
19
19
 
20
- /* Interop Constraints */
21
- "isolatedModules": true,
22
- "verbatimModuleSyntax": true,
23
- "allowSyntheticDefaultImports": true,
24
- "esModuleInterop": true,
25
- "forceConsistentCasingInFileNames": true,
20
+ /* Interop Constraints */
21
+ "isolatedModules": true,
22
+ "verbatimModuleSyntax": false,
23
+ "allowSyntheticDefaultImports": true,
24
+ "esModuleInterop": true,
25
+ "forceConsistentCasingInFileNames": true,
26
26
 
27
- /* Type Checking */
28
- "strict": true,
29
- "strictNullChecks": true,
30
- "noUnusedLocals": true,
31
- "noUnusedParameters": true,
32
- "exactOptionalPropertyTypes": true,
33
- "noImplicitReturns": true,
34
- "noFallthroughCasesInSwitch": true,
35
- "noUncheckedIndexedAccess": true,
36
- "noImplicitOverride": true,
37
- "noPropertyAccessFromIndexSignature": true,
38
- "allowUnusedLabels": false,
39
- "allowUnreachableCode": false,
27
+ /* Type Checking */
28
+ "strict": true,
29
+ "strictNullChecks": true,
30
+ "noUnusedLocals": true,
31
+ "noUnusedParameters": true,
32
+ "exactOptionalPropertyTypes": true,
33
+ "noImplicitReturns": true,
34
+ "noFallthroughCasesInSwitch": true,
35
+ "noUncheckedIndexedAccess": true,
36
+ "noImplicitOverride": true,
37
+ "noPropertyAccessFromIndexSignature": true,
38
+ "allowUnusedLabels": false,
39
+ "allowUnreachableCode": false,
40
40
 
41
- /* Completeness */
42
- "skipLibCheck": true
43
- },
44
- "include": ["src"]
41
+ /* Completeness */
42
+ "skipLibCheck": true
43
+ },
44
+ "include": ["src"]
45
45
  }