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.
@@ -21,12 +21,18 @@ type EventCallbackFunctions = {
21
21
  delete_message: (data: DeleteMessage) => unknown;
22
22
  event: (event: Event) => unknown;
23
23
  raw_message: (message: IRC_Message) => unknown;
24
+ connected: () => unknown;
25
+ auth_error: () => unknown;
24
26
  };
25
27
 
26
28
  type EventNames = keyof EventCallbackFunctions;
27
29
 
28
30
  export class TwitchIRC {
29
31
  public channel_name?: string;
32
+ public auth?: { username: string; token: string } | undefined;
33
+ public bot_name?: string;
34
+ public bot_token?: string;
35
+
30
36
  private assets: {
31
37
  external_emotes: EmoteURLsByName;
32
38
  badges: BadgeURLsBySetIDOrSetIDAndVersion;
@@ -45,10 +51,11 @@ export class TwitchIRC {
45
51
  private public_listeners: Partial<EventCallbackFunctions> = {};
46
52
 
47
53
  public socket?: WebSocket;
48
- public ws?: WebSocket | undefined;
54
+ public wsCustom?: WebSocket | undefined;
49
55
 
50
- constructor(ws?: WebSocket) {
51
- this.ws = ws;
56
+ constructor(options: { ws?: WebSocket; auth?: { username: string; token: string } }) {
57
+ this.wsCustom = options.ws;
58
+ this.auth = options.auth;
52
59
  }
53
60
 
54
61
  public setBadges(badges: BadgeURLsBySetIDOrSetIDAndVersion) {
@@ -67,6 +74,11 @@ export class TwitchIRC {
67
74
  return this.assets.external_emotes;
68
75
  }
69
76
 
77
+ public authenticate(username: string, token: string) {
78
+ this.bot_name = username;
79
+ this.bot_token = token;
80
+ }
81
+
70
82
  public connect(channel?: { channelName?: string }) {
71
83
  if (channel?.channelName) this.channel_name = channel.channelName;
72
84
  if (!this.channel_name) return console.error('channel_name not specified');
@@ -76,14 +88,24 @@ export class TwitchIRC {
76
88
  this.socket?.close();
77
89
 
78
90
  this.socket = new WebSocket('wss://irc-ws.chat.twitch.tv', null, {
79
- WebSocket: this.ws,
91
+ WebSocket: this.wsCustom,
80
92
  });
81
93
  this.socket.onopen = () => this.onOpen();
82
94
  this.socket.onclose = () => this.onClose();
83
95
  this.socket.onmessage = (event) => this.onMessage(event);
84
96
  }
85
97
 
98
+ public send(message: string, replyParentMessageId?: string) {
99
+ if (this.auth === undefined) return console.error('No Auth Information');
100
+ if (!this.isConnected()) return console.error('Not Connected');
101
+
102
+ this.socket.send(
103
+ `${replyParentMessageId ? `@reply-parent-msg-id=${replyParentMessageId} ` : ''}PRIVMSG #${this.channel_name} :${message}`,
104
+ );
105
+ }
106
+
86
107
  public disconnect() {
108
+ this.socket?.send(`Part #${this.channel_name}`);
87
109
  this.socket?.close();
88
110
  }
89
111
 
@@ -99,12 +121,18 @@ export class TwitchIRC {
99
121
  }
100
122
 
101
123
  private onOpen() {
102
- this.send('CAP REQ :twitch.tv/commands twitch.tv/tags');
103
- this.send(`PASS ${ANONYMOUS_IRC_PASS}`);
104
- this.send(`NICK ${ANONYMOUS_IRC_LOGIN}`);
105
- this.send(`JOIN #${this.channel_name}`);
124
+ this.sendIRC('CAP REQ :twitch.tv/commands twitch.tv/tags');
125
+ if (this.auth !== undefined) {
126
+ this.sendIRC(`PASS oauth:${this.auth.token}`);
127
+ this.sendIRC(`NICK ${this.auth.username}`);
128
+ this.sendIRC(`JOIN #${this.channel_name}`);
129
+ } else {
130
+ this.sendIRC(`PASS ${ANONYMOUS_IRC_PASS}`);
131
+ this.sendIRC(`NICK ${ANONYMOUS_IRC_LOGIN}`);
132
+ this.sendIRC(`JOIN #${this.channel_name}`);
133
+ }
106
134
 
107
- console.log(`Connected to Twitch IRC as Anonymous (${this.channel_name})`);
135
+ this.public_listeners.connected?.();
108
136
 
109
137
  if (this.ping.interval) clearInterval(this.ping.interval);
110
138
  this.sendPing();
@@ -117,7 +145,7 @@ export class TwitchIRC {
117
145
  }
118
146
 
119
147
  private sendPing() {
120
- this.send('PING');
148
+ this.sendIRC('PING');
121
149
  this.ping.lastSentTimestamp = Date.now();
122
150
 
123
151
  if (this.ping.timeout) clearTimeout(this.ping.timeout);
@@ -127,10 +155,9 @@ export class TwitchIRC {
127
155
  }, PING_TIMEOUT_MS);
128
156
  }
129
157
 
130
- private send(irc_message: string) {
131
- if (!this.isConnected()) {
132
- throw new Error('Not connected');
133
- }
158
+ private sendIRC(irc_message: string) {
159
+ if (!this.isConnected()) return console.error('Not Connected');
160
+
134
161
  this.socket?.send(irc_message);
135
162
  }
136
163
 
@@ -149,7 +176,26 @@ export class TwitchIRC {
149
176
  break;
150
177
  }
151
178
  case 'PING': {
152
- this.send('PONG');
179
+ this.sendIRC('PONG');
180
+ break;
181
+ }
182
+ case '001': {
183
+ console.log(
184
+ `Connected to Twitch IRC as ${this.auth?.username ?? 'Anonymous'} (${this.channel_name})`,
185
+ );
186
+
187
+ this.public_listeners.connected?.();
188
+ break;
189
+ }
190
+ case 'NOTICE': {
191
+ if (
192
+ message.params[0] === 'Login authentication failed' ||
193
+ message.params[0] === 'Improperly formatted auth'
194
+ ) {
195
+ clearTimeout(this.ping.timeout);
196
+ this.disconnect();
197
+ this.public_listeners.auth_error?.();
198
+ }
153
199
  break;
154
200
  }
155
201
  case 'CLEARCHAT': {
@@ -316,7 +362,7 @@ function parseIRCLine(line: string): IRC_Message {
316
362
  componentIndex++;
317
363
 
318
364
  let channel: string = '';
319
- if (components[componentIndex]!.startsWith('#')) channel = components[componentIndex]!.slice(1);
365
+ if (components[componentIndex]?.startsWith('#')) channel = components[componentIndex]!.slice(1);
320
366
  componentIndex++;
321
367
 
322
368
  const params: string[] = [];
@@ -430,6 +476,8 @@ const RAW_TAGS = {
430
476
 
431
477
  PONG: [],
432
478
 
479
+ '001': [],
480
+
433
481
  PRIVMSG: [
434
482
  'badge-info',
435
483
  'badges',