multichat-ts 0.0.2 → 0.0.4

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 CHANGED
@@ -40,14 +40,17 @@ kick_chat.on('message', (message) => {
40
40
 
41
41
  ```svelte
42
42
  <script lang="ts">
43
- import { TwitchIRC, type Message, KickPusher } from 'multichat-ts';
43
+ import { TwitchIRC, type Message } from 'multichat-ts';
44
44
 
45
45
  let messages: Message[] = $state([]);
46
46
 
47
47
  const twitch_chat = new TwitchIRC();
48
+ $effect(() => {
49
+ twitch_chat.connect({
50
+ channelName: 'piratesoftware'
51
+ });
48
52
 
49
- twitch_chat.connect({
50
- channelName: 'piratesoftware'
53
+ return () => twitch_chat.disconnect();
51
54
  });
52
55
 
53
56
  twitch_chat.on('message', (message) => {
package/dist/index.d.ts CHANGED
@@ -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/kick.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import Pusher from 'pusher-js';
2
2
  import { type BadgeURLsByNameOrCount, type EmoteURLsByName, type Message } from './index.js';
3
3
  type EventCallbackFunctions = {
4
- message: (data: Message) => unknown;
4
+ message: (message: Message) => unknown;
5
+ raw_message: (message: ChatMessageEvent) => unknown;
5
6
  };
6
7
  type EventNames = keyof EventCallbackFunctions;
7
8
  export declare class KickPusher {
@@ -22,4 +23,24 @@ export declare class KickPusher {
22
23
  private onSubscriptionSuccess;
23
24
  private onChatMessage;
24
25
  }
26
+ interface ChatMessageEvent {
27
+ id: string;
28
+ chatroom_id: number;
29
+ content: string;
30
+ type: string;
31
+ created_at: string;
32
+ sender: {
33
+ id: number;
34
+ username: string;
35
+ slug?: string;
36
+ identity: {
37
+ color: string;
38
+ badges: Array<{
39
+ type: string;
40
+ text: string;
41
+ count?: number;
42
+ }>;
43
+ };
44
+ };
45
+ }
25
46
  export {};
package/dist/kick.js CHANGED
@@ -62,7 +62,7 @@ export class KickPusher {
62
62
  console.log(`Connected to Kick Pusher (${this.channel_name})`);
63
63
  }
64
64
  onChatMessage(data) {
65
- console.log('onMessage', data);
65
+ this.public_listeners.raw_message?.(data);
66
66
  const text = data.content;
67
67
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
68
68
  console.log(emote_matches);
package/dist/twitch.d.ts CHANGED
@@ -1,22 +1,25 @@
1
1
  import { WebSocket } from 'partysocket';
2
- import { type ClearMessages, type DeleteMessage, type Message, type Event, type EmoteURLsByName, type BadgeURLsBySetIDAndVersionID } from './index.js';
2
+ import { type ClearMessages, type DeleteMessage, type Message, type Event, type EmoteURLsByName, type BadgeURLsBySetIDAndVersionIDOrAny } from './index.js';
3
3
  type EventCallbackFunctions = {
4
- message: (data: Message) => unknown;
4
+ message: (message: Message) => unknown;
5
5
  clear_messages: (data: ClearMessages) => unknown;
6
6
  delete_message: (data: DeleteMessage) => unknown;
7
- event: (data: Event) => unknown;
7
+ event: (event: Event) => unknown;
8
+ raw_message: (message: IRC_Message) => unknown;
8
9
  };
9
10
  type EventNames = keyof EventCallbackFunctions;
10
11
  export declare class TwitchIRC {
11
12
  channel_name?: string;
12
13
  assets: {
13
14
  external_emotes: EmoteURLsByName;
14
- badges: BadgeURLsBySetIDAndVersionID;
15
+ badges: BadgeURLsBySetIDAndVersionIDOrAny;
15
16
  };
16
17
  latency: number;
17
18
  private ping;
18
19
  private public_listeners;
19
20
  socket?: WebSocket;
21
+ ws?: WebSocket | undefined;
22
+ constructor(ws?: WebSocket);
20
23
  connect(channel?: {
21
24
  channelName?: string;
22
25
  }): void;
@@ -33,4 +36,37 @@ export declare class TwitchIRC {
33
36
  private send;
34
37
  private onMessage;
35
38
  }
39
+ type IRC_Message = {
40
+ [C in CommandType]: {
41
+ channel: string;
42
+ command: C;
43
+ params: string[];
44
+ source: RawSource | undefined;
45
+ tags: SpecificRawTags<C> | undefined;
46
+ };
47
+ }[CommandType];
48
+ type RAW_TAGS = typeof RAW_TAGS;
49
+ type CommandType = keyof RAW_TAGS;
50
+ type SpecificRawTags<Command extends CommandType> = Record<RAW_TAGS[Command][number], string | undefined>;
51
+ type RawSource = {
52
+ host: string;
53
+ nick?: string | undefined;
54
+ user?: string | undefined;
55
+ };
56
+ declare const RAW_TAGS: {
57
+ readonly CLEARCHAT: readonly ["ban-duration", "room-id", "target-user-id", "tmi-sent-ts"];
58
+ readonly CLEARMSG: readonly ["login", "room-id", "target-msg-id", "tmi-sent-ts"];
59
+ readonly GLOBALUSERSTATE: readonly ["badge-info", "badges", "color", "display-name", "emote-sets", "turbo", "user-id", "user-type"];
60
+ readonly HOSTTARGET: readonly [];
61
+ readonly NOTICE: readonly ["msg-id", "target-user-id"];
62
+ readonly PART: readonly [];
63
+ readonly PING: readonly [];
64
+ readonly PONG: readonly [];
65
+ 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"];
66
+ readonly RECONNECT: readonly [];
67
+ readonly ROOMSTATE: readonly ["emote-only", "followers-only", "r9k", "room-id", "slow", "subs-only"];
68
+ 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"];
69
+ readonly USERSTATE: readonly ["badge-info", "badges", "color", "display-name", "emote-sets", "id", "mod", "subscriber", "turbo", "user-type"];
70
+ readonly WHISPER: readonly ["badges", "color", "display-name", "emotes", "message-id", "thread-id", "turbo", "user-id", "user-type"];
71
+ };
36
72
  export {};
package/dist/twitch.js CHANGED
@@ -13,6 +13,10 @@ export class TwitchIRC {
13
13
  ping = {};
14
14
  public_listeners = {};
15
15
  socket;
16
+ ws;
17
+ constructor(ws) {
18
+ this.ws = ws;
19
+ }
16
20
  connect(channel) {
17
21
  if (channel?.channelName)
18
22
  this.channel_name = channel.channelName;
@@ -20,11 +24,12 @@ export class TwitchIRC {
20
24
  return console.error('channel_name not specified');
21
25
  console.log(`connecting to ${this.channel_name}...`);
22
26
  this.socket?.close();
23
- 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
+ });
24
30
  this.socket.onopen = () => this.onOpen();
25
31
  this.socket.onclose = () => this.onClose();
26
32
  this.socket.onmessage = (event) => this.onMessage(event);
27
- 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`));
28
33
  }
29
34
  disconnect() {
30
35
  this.socket?.close();
@@ -72,6 +77,7 @@ export class TwitchIRC {
72
77
  const lines = String(event.data).trim().split('\r\n');
73
78
  const messages = lines.map(parseIRCLine);
74
79
  messages.forEach((message) => {
80
+ this.public_listeners.raw_message?.(message);
75
81
  switch (message.command) {
76
82
  case 'PONG': {
77
83
  if (!this.ping.lastSentTimestamp)
@@ -183,7 +189,7 @@ export class TwitchIRC {
183
189
  const [set_id, version] = badge.split('/');
184
190
  if (!set_id || !version)
185
191
  return [];
186
- const url = this.assets.badges[set_id]?.[version];
192
+ const url = this.assets.badges[set_id]?.[version] ?? this.assets.badges[set_id]?.['*'];
187
193
  if (!url)
188
194
  return [];
189
195
  return {
@@ -194,7 +200,7 @@ export class TwitchIRC {
194
200
  }) ?? [],
195
201
  color: tags.color ?? '#FFFFFF',
196
202
  id: tags['user-id'],
197
- username: 'IDK',
203
+ username: (tags['display-name'] ?? 'Unknown').toLowerCase(),
198
204
  display_name: tags['display-name'] ?? 'Unknown',
199
205
  roles: {},
200
206
  },
package/package.json CHANGED
@@ -1,52 +1,54 @@
1
1
  {
2
- "name": "multichat-ts",
3
- "version": "0.0.2",
4
- "type": "module",
5
- "description": "Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/0Abdullah/multichat-ts.git"
9
- },
10
- "homepage": "https://github.com/0Abdullah/multichat-ts",
11
- "main": "dist/index.js",
12
- "types": "dist/index.d.ts",
13
- "exports": {
14
- ".": "./dist/index.js"
15
- },
16
- "keywords": [
17
- "chat",
18
- "multichat",
19
- "twitch",
20
- "kick",
21
- "realtime",
22
- "irc",
23
- "pusher",
24
- "typescript",
25
- "ts"
26
- ],
27
- "author": {
28
- "name": "Abdullah",
29
- "url": "https://github.com/0Abdullah"
30
- },
31
- "license": "MIT",
32
- "devDependencies": {
33
- "@eslint/js": "^9.10.0",
34
- "@types/node": "^22.5.4",
35
- "eslint": "^9.10.0",
36
- "eslint-config-prettier": "^9.1.0",
37
- "prettier": "^3.3.3",
38
- "typescript": "^5.5.4",
39
- "typescript-eslint": "^8.4.0"
40
- },
41
- "dependencies": {
42
- "partysocket": "^1.0.2",
43
- "pusher-js": "8.4.0-rc2"
44
- },
45
- "scripts": {
46
- "build": "tsc",
47
- "lint": "eslint .",
48
- "lint:fix": "eslint . --fix",
49
- "format": "prettier --check src",
50
- "format:fix": "prettier --write src"
51
- }
52
- }
2
+ "name": "multichat-ts",
3
+ "version": "0.0.4",
4
+ "type": "module",
5
+ "description": "Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/0Abdullah/multichat-ts.git"
9
+ },
10
+ "homepage": "https://github.com/0Abdullah/multichat-ts",
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": "./dist/index.js"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "prepublishOnly": "pnpm build",
19
+ "pub": "npm publish --access public",
20
+ "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
22
+ "format": "prettier --check src",
23
+ "format:fix": "prettier --write src"
24
+ },
25
+ "keywords": [
26
+ "chat",
27
+ "multichat",
28
+ "twitch",
29
+ "kick",
30
+ "realtime",
31
+ "irc",
32
+ "pusher",
33
+ "typescript",
34
+ "ts"
35
+ ],
36
+ "author": {
37
+ "name": "Abdullah",
38
+ "url": "https://github.com/0Abdullah"
39
+ },
40
+ "license": "MIT",
41
+ "devDependencies": {
42
+ "@eslint/js": "^9.10.0",
43
+ "@types/node": "^22.5.4",
44
+ "eslint": "^9.10.0",
45
+ "eslint-config-prettier": "^9.1.0",
46
+ "prettier": "^3.3.3",
47
+ "typescript": "^5.5.4",
48
+ "typescript-eslint": "^8.4.0"
49
+ },
50
+ "dependencies": {
51
+ "partysocket": "^1.0.2",
52
+ "pusher-js": "8.4.0-rc2"
53
+ }
54
+ }
package/src/index.ts CHANGED
@@ -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
@@ -8,7 +8,8 @@ import {
8
8
  } from './index.js';
9
9
 
10
10
  type EventCallbackFunctions = {
11
- message: (data: Message) => unknown;
11
+ message: (message: Message) => unknown;
12
+ raw_message: (message: ChatMessageEvent) => unknown;
12
13
  };
13
14
 
14
15
  type EventNames = keyof EventCallbackFunctions;
@@ -109,8 +110,7 @@ export class KickPusher {
109
110
  }
110
111
 
111
112
  private onChatMessage(data: ChatMessageEvent) {
112
- console.log('onMessage', data);
113
-
113
+ this.public_listeners.raw_message?.(data);
114
114
  const text = data.content;
115
115
  const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
116
116
  console.log(emote_matches);
package/src/twitch.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  type Message,
8
8
  type Event,
9
9
  type EmoteURLsByName,
10
- type BadgeURLsBySetIDAndVersionID,
10
+ type BadgeURLsBySetIDAndVersionIDOrAny,
11
11
  } from './index.js';
12
12
 
13
13
  const ANONYMOUS_IRC_PASS = 'SCHMOOPIIE';
@@ -16,10 +16,11 @@ const PING_INTERVAL_MS = 30_000;
16
16
  const PING_TIMEOUT_MS = 10_000;
17
17
 
18
18
  type EventCallbackFunctions = {
19
- message: (data: Message) => unknown;
19
+ message: (message: Message) => unknown;
20
20
  clear_messages: (data: ClearMessages) => unknown;
21
21
  delete_message: (data: DeleteMessage) => unknown;
22
- event: (data: Event) => unknown;
22
+ event: (event: Event) => unknown;
23
+ raw_message: (message: IRC_Message) => unknown;
23
24
  };
24
25
 
25
26
  type EventNames = keyof EventCallbackFunctions;
@@ -28,7 +29,7 @@ export class TwitchIRC {
28
29
  public channel_name?: string;
29
30
  public assets: {
30
31
  external_emotes: EmoteURLsByName;
31
- badges: BadgeURLsBySetIDAndVersionID;
32
+ badges: BadgeURLsBySetIDAndVersionIDOrAny;
32
33
  } = {
33
34
  external_emotes: {},
34
35
  badges: {},
@@ -44,6 +45,11 @@ export class TwitchIRC {
44
45
  private public_listeners: Partial<EventCallbackFunctions> = {};
45
46
 
46
47
  public socket?: WebSocket;
48
+ public ws?: WebSocket | undefined;
49
+
50
+ constructor(ws?: WebSocket) {
51
+ this.ws = ws;
52
+ }
47
53
 
48
54
  public connect(channel?: { channelName?: string }) {
49
55
  if (channel?.channelName) this.channel_name = channel.channelName;
@@ -53,17 +59,12 @@ export class TwitchIRC {
53
59
 
54
60
  this.socket?.close();
55
61
 
56
- this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv');
62
+ this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
63
+ WebSocket: this.ws,
64
+ });
57
65
  this.socket.onopen = () => this.onOpen();
58
66
  this.socket.onclose = () => this.onClose();
59
67
  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
68
  }
68
69
 
69
70
  public disconnect() {
@@ -122,6 +123,7 @@ export class TwitchIRC {
122
123
  const lines = String(event.data).trim().split('\r\n');
123
124
  const messages = lines.map(parseIRCLine);
124
125
  messages.forEach((message) => {
126
+ this.public_listeners.raw_message?.(message);
125
127
  switch (message.command) {
126
128
  case 'PONG': {
127
129
  if (!this.ping.lastSentTimestamp) return console.error('got PONG without sending PING');
@@ -244,7 +246,8 @@ export class TwitchIRC {
244
246
  const [set_id, version] = badge.split('/');
245
247
  if (!set_id || !version) return [];
246
248
 
247
- const url = this.assets.badges[set_id]?.[version];
249
+ const url =
250
+ this.assets.badges[set_id]?.[version] ?? this.assets.badges[set_id]?.['*'];
248
251
  if (!url) return [];
249
252
 
250
253
  return {
@@ -255,7 +258,7 @@ export class TwitchIRC {
255
258
  }) ?? [],
256
259
  color: tags.color ?? '#FFFFFF',
257
260
  id: tags['user-id'],
258
- username: 'IDK',
261
+ username: (tags['display-name'] ?? 'Unknown').toLowerCase(),
259
262
  display_name: tags['display-name'] ?? 'Unknown',
260
263
  roles: {},
261
264
  },