multichat-ts 0.0.93 → 0.0.95

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/.prettierrc CHANGED
@@ -1,7 +1,7 @@
1
- {
2
- "useTabs": true,
3
- "singleQuote": true,
4
- "semi": true,
5
- "trailingComma": "all",
6
- "printWidth": 100
7
- }
1
+ {
2
+ "useTabs": true,
3
+ "singleQuote": true,
4
+ "semi": true,
5
+ "trailingComma": "all",
6
+ "printWidth": 100
7
+ }
package/dist/kick.js CHANGED
@@ -121,7 +121,7 @@ export class KickPusher {
121
121
  onChatMessage(data) {
122
122
  this.public_listeners.raw_message?.(data);
123
123
  const text = data.content;
124
- const emote_matches = [...data.content.matchAll(/\[emote:\d+:[a-zA-Z0-9]*\]/g)];
124
+ const emote_matches = [...data.content.matchAll(/\[emote:\d+:.+\]/g)];
125
125
  const body = [];
126
126
  emote_matches.forEach((match) => {
127
127
  const emote_string = match[0];
package/dist/twitch.d.ts CHANGED
@@ -6,24 +6,40 @@ type EventCallbackFunctions = {
6
6
  delete_message: (data: DeleteMessage) => unknown;
7
7
  event: (event: Event) => unknown;
8
8
  raw_message: (message: IRC_Message) => unknown;
9
+ connected: () => unknown;
10
+ auth_error: () => unknown;
9
11
  };
10
12
  type EventNames = keyof EventCallbackFunctions;
11
13
  export declare class TwitchIRC {
12
14
  channel_name?: string;
15
+ auth?: {
16
+ username: string;
17
+ token: string;
18
+ } | undefined;
19
+ bot_name?: string;
20
+ bot_token?: string;
13
21
  private assets;
14
22
  latency: number;
15
23
  private ping;
16
24
  private public_listeners;
17
25
  socket?: WebSocket;
18
- ws?: WebSocket | undefined;
19
- constructor(ws?: WebSocket);
26
+ wsCustom?: WebSocket | undefined;
27
+ constructor(options: {
28
+ ws?: WebSocket;
29
+ auth?: {
30
+ username: string;
31
+ token: string;
32
+ };
33
+ });
20
34
  setBadges(badges: BadgeURLsBySetIDOrSetIDAndVersion): void;
21
35
  setExternalEmotes(external_emotes: EmoteURLsByName): void;
22
36
  getStoredBadges(): BadgeURLsBySetIDOrSetIDAndVersion;
23
37
  getStoredExternalEmotes(): EmoteURLsByName;
38
+ authenticate(username: string, token: string): void;
24
39
  connect(channel?: {
25
40
  channelName?: string;
26
41
  }): void;
42
+ send(message: string, replyParentMessageId?: string): void;
27
43
  disconnect(): void;
28
44
  on<EventName extends EventNames>(event_name: EventName, callback_fn: EventCallbackFunctions[EventName]): void;
29
45
  isConnected(): this is {
@@ -34,7 +50,7 @@ export declare class TwitchIRC {
34
50
  private onOpen;
35
51
  private onClose;
36
52
  private sendPing;
37
- private send;
53
+ private sendIRC;
38
54
  private onMessage;
39
55
  }
40
56
  type IRC_Message = {
@@ -63,6 +79,7 @@ declare const RAW_TAGS: {
63
79
  readonly PART: readonly [];
64
80
  readonly PING: readonly [];
65
81
  readonly PONG: readonly [];
82
+ readonly '001': readonly [];
66
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"];
67
84
  readonly RECONNECT: readonly [];
68
85
  readonly ROOMSTATE: readonly ["emote-only", "followers-only", "r9k", "room-id", "slow", "subs-only"];
package/dist/twitch.js CHANGED
@@ -5,6 +5,9 @@ const PING_INTERVAL_MS = 30_000;
5
5
  const PING_TIMEOUT_MS = 10_000;
6
6
  export class TwitchIRC {
7
7
  channel_name;
8
+ auth;
9
+ bot_name;
10
+ bot_token;
8
11
  assets = {
9
12
  external_emotes: {},
10
13
  badges: {},
@@ -13,9 +16,10 @@ export class TwitchIRC {
13
16
  ping = {};
14
17
  public_listeners = {};
15
18
  socket;
16
- ws;
17
- constructor(ws) {
18
- this.ws = ws;
19
+ wsCustom;
20
+ constructor(options) {
21
+ this.wsCustom = options.ws;
22
+ this.auth = options.auth;
19
23
  }
20
24
  setBadges(badges) {
21
25
  this.assets.badges = { ...badges, ...this.assets.badges };
@@ -29,6 +33,10 @@ export class TwitchIRC {
29
33
  getStoredExternalEmotes() {
30
34
  return this.assets.external_emotes;
31
35
  }
36
+ authenticate(username, token) {
37
+ this.bot_name = username;
38
+ this.bot_token = token;
39
+ }
32
40
  connect(channel) {
33
41
  if (channel?.channelName)
34
42
  this.channel_name = channel.channelName;
@@ -37,13 +45,21 @@ export class TwitchIRC {
37
45
  console.log(`connecting to ${this.channel_name}...`);
38
46
  this.socket?.close();
39
47
  this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
40
- WebSocket: this.ws,
48
+ WebSocket: this.wsCustom,
41
49
  });
42
50
  this.socket.onopen = () => this.onOpen();
43
51
  this.socket.onclose = () => this.onClose();
44
52
  this.socket.onmessage = (event) => this.onMessage(event);
45
53
  }
54
+ send(message, replyParentMessageId) {
55
+ if (this.auth === undefined)
56
+ return console.error('No Auth Information');
57
+ if (!this.isConnected())
58
+ return console.error('Not Connected');
59
+ this.socket.send(`${replyParentMessageId ? `@reply-parent-msg-id=${replyParentMessageId} ` : ''}PRIVMSG #${this.channel_name} :${message}`);
60
+ }
46
61
  disconnect() {
62
+ this.socket?.send(`Part #${this.channel_name}`);
47
63
  this.socket?.close();
48
64
  }
49
65
  on(event_name, callback_fn) {
@@ -53,11 +69,18 @@ export class TwitchIRC {
53
69
  return !!this.socket && this.socket.readyState === WebSocket.OPEN;
54
70
  }
55
71
  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})`);
72
+ this.sendIRC('CAP REQ :twitch.tv/commands twitch.tv/tags');
73
+ if (this.auth !== undefined) {
74
+ this.sendIRC(`PASS oauth:${this.auth.token}`);
75
+ this.sendIRC(`NICK ${this.auth.username}`);
76
+ this.sendIRC(`JOIN #${this.channel_name}`);
77
+ }
78
+ else {
79
+ this.sendIRC(`PASS ${ANONYMOUS_IRC_PASS}`);
80
+ this.sendIRC(`NICK ${ANONYMOUS_IRC_LOGIN}`);
81
+ this.sendIRC(`JOIN #${this.channel_name}`);
82
+ }
83
+ this.public_listeners.connected?.();
61
84
  if (this.ping.interval)
62
85
  clearInterval(this.ping.interval);
63
86
  this.sendPing();
@@ -68,7 +91,7 @@ export class TwitchIRC {
68
91
  clearTimeout(this.ping.timeout);
69
92
  }
70
93
  sendPing() {
71
- this.send('PING');
94
+ this.sendIRC('PING');
72
95
  this.ping.lastSentTimestamp = Date.now();
73
96
  if (this.ping.timeout)
74
97
  clearTimeout(this.ping.timeout);
@@ -77,10 +100,9 @@ export class TwitchIRC {
77
100
  this.connect();
78
101
  }, PING_TIMEOUT_MS);
79
102
  }
80
- send(irc_message) {
81
- if (!this.isConnected()) {
82
- throw new Error('Not connected');
83
- }
103
+ sendIRC(irc_message) {
104
+ if (!this.isConnected())
105
+ return console.error('Not Connected');
84
106
  this.socket?.send(irc_message);
85
107
  }
86
108
  onMessage(event) {
@@ -100,7 +122,21 @@ export class TwitchIRC {
100
122
  break;
101
123
  }
102
124
  case 'PING': {
103
- this.send('PONG');
125
+ this.sendIRC('PONG');
126
+ break;
127
+ }
128
+ case '001': {
129
+ console.log(`Connected to Twitch IRC as ${this.auth?.username ?? 'Anonymous'} (${this.channel_name})`);
130
+ this.public_listeners.connected?.();
131
+ break;
132
+ }
133
+ case 'NOTICE': {
134
+ if (message.params[0] === 'Login authentication failed' ||
135
+ message.params[0] === 'Improperly formatted auth') {
136
+ clearTimeout(this.ping.timeout);
137
+ this.disconnect();
138
+ this.public_listeners.auth_error?.();
139
+ }
104
140
  break;
105
141
  }
106
142
  case 'CLEARCHAT': {
@@ -249,7 +285,7 @@ function parseIRCLine(line) {
249
285
  const command = components[componentIndex];
250
286
  componentIndex++;
251
287
  let channel = '';
252
- if (components[componentIndex].startsWith('#'))
288
+ if (components[componentIndex]?.startsWith('#'))
253
289
  channel = components[componentIndex].slice(1);
254
290
  componentIndex++;
255
291
  const params = [];
@@ -317,6 +353,7 @@ const RAW_TAGS = {
317
353
  PART: [],
318
354
  PING: [],
319
355
  PONG: [],
356
+ '001': [],
320
357
  PRIVMSG: [
321
358
  'badge-info',
322
359
  'badges',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multichat-ts",
3
- "version": "0.0.93",
3
+ "version": "0.0.95",
4
4
  "type": "module",
5
5
  "description": "Receive type-safe realtime events for chat-related messages on multiple platforms (Twitch, Kick)",
6
6
  "repository": {
@@ -1,173 +1,173 @@
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
- };
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
+ };