split IRC client off so it can be used by script or worker
This commit is contained in:
parent
e8c9c293df
commit
c56030b589
328
v5shoutbox.js
328
v5shoutbox.js
|
@ -37,317 +37,39 @@ authentication setup: normal IRC (with password), PCv4, PCv5.
|
|||
*/
|
||||
|
||||
"use strict";
|
||||
import * as IRC from "./v5shoutbox_irc.js";
|
||||
|
||||
/* WebSocket IRC with HTTP-based website interface. */
|
||||
let irc = new function() {
|
||||
|
||||
const State = Object.freeze({
|
||||
/* Websocket is disconnected. */
|
||||
DISCONNECTED: Symbol("DISCONNECTED"),
|
||||
/* We just opened a socket to the IRC server and have yet to register. */
|
||||
CONNECTING: Symbol("CONNECTING"),
|
||||
/* We are authenticated and ready to use the server. */
|
||||
READY: Symbol("READY"),
|
||||
});
|
||||
|
||||
let conn = {
|
||||
/* IRC server's websocket URL (wss://...). */
|
||||
serverUrl: undefined,
|
||||
/* The websocket connected to serverUrl */
|
||||
socket: undefined,
|
||||
/* HTTP-based message post URL. Undefined when using pure IRC, or a v4/v5
|
||||
URL when using website authentication. */
|
||||
postUrl: undefined,
|
||||
/* Current connection state. */
|
||||
state: State.DISCONNECTED,
|
||||
/* Username with which we are logged in. Undefined when DISCONNECTED. */
|
||||
username: undefined,
|
||||
/* Current channel. This remains defined even when DISCONNECTED. */
|
||||
channel: undefined,
|
||||
/* Password for connection. Erased after authenticating. */
|
||||
password: undefined,
|
||||
/* Capabilities enabled. */
|
||||
caps: [],
|
||||
};
|
||||
|
||||
let batches = new Map();
|
||||
|
||||
/* List of channels and their information */
|
||||
this.channels = new Map();
|
||||
|
||||
/*** Accessors/mutators ***/
|
||||
|
||||
this.isConnected = function() {
|
||||
return conn.state !== State.DISCONNECTED;
|
||||
}
|
||||
this.isRunning = function() {
|
||||
return conn.state === State.READY;
|
||||
}
|
||||
this.stateString = function() {
|
||||
switch(conn.state) {
|
||||
case State.DISCONNECTED: return "Disconnected";
|
||||
case State.CONNECTED: return "Connecting...";
|
||||
case State.READY: return "Connected";
|
||||
}
|
||||
const registerServiceWorker = async () => {
|
||||
if(!("serviceWorker" in navigator)) {
|
||||
// TODO: Use previous, local IRC client
|
||||
console.error("Service workers not supported, shoutbox not available.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentChannel = function() {
|
||||
return conn.channel;
|
||||
}
|
||||
this.selectChannel = function(name) {
|
||||
conn.channel = name;
|
||||
}
|
||||
this.isOnRemoteChannel = function() {
|
||||
const c = conn.channel;
|
||||
return (c !== undefined) && (c.startsWith("&") || c.startsWith("#"));
|
||||
}
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register(
|
||||
'v5shoutbox_worker.js', { scope: "./" });
|
||||
|
||||
/*** Overridable hooks and callbacks ***/
|
||||
if (registration.installing)
|
||||
console.log('Service worker installing');
|
||||
else if (registration.waiting)
|
||||
console.log('Service worker installed');
|
||||
else if (registration.active)
|
||||
console.log('Service worker active');
|
||||
|
||||
this.onLog = function(message) {};
|
||||
this.onStateChanged = function() {};
|
||||
this.onAuthenticated = function() {};
|
||||
this.onNewMessage = function(channel, date, author, message) {};
|
||||
this.onChannelChanged = function(channel) {};
|
||||
|
||||
function log(...args) {
|
||||
irc.onLog(...args);
|
||||
}
|
||||
|
||||
/*** Connection management ***/
|
||||
|
||||
function setState(state) {
|
||||
conn.state = state;
|
||||
irc.onStateChanged();
|
||||
}
|
||||
|
||||
this.connect = function(serverUrl, postUrl, username, password) {
|
||||
conn.serverUrl = serverUrl;
|
||||
conn.postUrl = postUrl;
|
||||
conn.socket = new WebSocket(conn.serverUrl);
|
||||
log("[v5shoutbox] Connecting...");
|
||||
|
||||
conn.socket.onopen = function() {
|
||||
log("[v5shoutbox] Connected.");
|
||||
setState(State.CONNECTED);
|
||||
conn.username = username;
|
||||
conn.password = password;
|
||||
startExchange();
|
||||
}
|
||||
conn.socket.onmessage = function(net) {
|
||||
log("[<] " + net.data);
|
||||
let msg = new Message(net.data);
|
||||
if(msg.command === undefined) {
|
||||
log("[v5shoutbox] invalid message");
|
||||
return;
|
||||
}
|
||||
processMessage(msg);
|
||||
}
|
||||
conn.socket.onclose = function() {
|
||||
log("[v5shoutbox] Disconnected.");
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
function sendRaw(command) {
|
||||
log("[>] " + command);
|
||||
conn.socket.send(command);
|
||||
}
|
||||
|
||||
function send(command, args) {
|
||||
if(args.slice(0,-1).some(x => x.includes(" ")))
|
||||
return log("invalid spaces in non-last args: " + command.toString() +
|
||||
" " + args.toString());
|
||||
|
||||
if(args.length && args[args.length - 1].includes(" "))
|
||||
args[args.length - 1] = ":" + args[args.length - 1];
|
||||
|
||||
sendRaw(command + " " + args.join(" "));
|
||||
}
|
||||
|
||||
/*** IRC protocol ***/
|
||||
|
||||
/* Regex for parsing messages */
|
||||
const rMessage = (function(){
|
||||
const rTags = /(?:@([^ ]+) +)?/;
|
||||
const rSource = /(?::([^ ]+) +)?/;
|
||||
const rCommand = /(?:([a-zA-Z]+)|(\d{3}))/;
|
||||
const rArgs = /((?: +[^\r\n :][^\r\n ]*)*)/;
|
||||
const rTrailing = /(?: +:([^\r\n]*))?/;
|
||||
return new RegExp([/^/, rTags, rSource, rCommand, rArgs, rTrailing, /$/]
|
||||
.map(r => r.source).join(""));
|
||||
})();
|
||||
|
||||
/* https://stackoverflow.com/questions/30106476 */
|
||||
function base64_encode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
function toSolidBytes(match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
class Message {
|
||||
constructor(text) {
|
||||
const matches = rMessage.exec(text);
|
||||
if(matches === undefined)
|
||||
return;
|
||||
|
||||
this.tags = new Map();
|
||||
(matches[1] || "").split(";").filter(s => s).forEach(s => {
|
||||
const parts = s.split(/=(.*)/);
|
||||
const value = (parts[1] || "").replaceAll("\\\\", "\\")
|
||||
.replaceAll("\\:", ";")
|
||||
.replaceAll("\\r", "\r")
|
||||
.replaceAll("\\n", "\n")
|
||||
.replaceAll(/\\./g, m => m[1]);
|
||||
this.tags.set(parts[0], value);
|
||||
});
|
||||
|
||||
this.source = matches[2];
|
||||
this.command = matches[3] || parseInt(matches[4]);
|
||||
this.args = (matches[5] || "").split(" ").filter(s => s);
|
||||
if(matches[6] !== undefined)
|
||||
this.args.push(matches[6]);
|
||||
}
|
||||
}
|
||||
this.Message = Message;
|
||||
|
||||
function startExchange() {
|
||||
sendRaw("CAP LS 302");
|
||||
send("NICK", [conn.username]);
|
||||
send("USER", [conn.username, "*", "0", conn.username]);
|
||||
}
|
||||
|
||||
function processMessage(msg) {
|
||||
if(msg.command === "PING") {
|
||||
send("PONG", msg.args);
|
||||
return;
|
||||
}
|
||||
if(msg.command === "BATCH") {
|
||||
const [mode, id] = [msg.args[0][0], msg.args[0].substring(1)];
|
||||
if(mode == "+")
|
||||
batches.set(id, []);
|
||||
batches.get(id).push(msg);
|
||||
if(mode == "-") {
|
||||
processBatch(batches.get(id));
|
||||
batches.delete(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(msg.tags.has("batch")) {
|
||||
batches.get(msg.tags.get("batch")).push(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(msg.command === "CAP" && msg.args[1] === "LS") {
|
||||
const caps = msg.args[2].split(" ").filter(x => x);
|
||||
const wishlist =
|
||||
["message-tags", "server-time", "echo-message",
|
||||
"batch", "sasl", "draft/chathistory"];
|
||||
|
||||
let req = wishlist.filter(x => caps.includes(x));
|
||||
if(caps.some(x => x.startsWith("sasl=")))
|
||||
req.push("sasl");
|
||||
|
||||
if(req.length)
|
||||
send("CAP", ["REQ", req.join(" ")]);
|
||||
else {
|
||||
conn.caps = [];
|
||||
sendRaw("CAP END");
|
||||
}
|
||||
}
|
||||
|
||||
if(msg.command === "CAP" && msg.args[1] === "ACK") {
|
||||
conn.caps = msg.args[2].split(" ").filter(x => x);
|
||||
if(conn.caps.includes("sasl"))
|
||||
sendRaw("AUTHENTICATE PLAIN");
|
||||
else
|
||||
sendRaw("CAP END");
|
||||
}
|
||||
if(msg.command === "AUTHENTICATE" && msg.args[0] === "+") {
|
||||
const arg = conn.username + "\0" + conn.username + "\0" + conn.password;
|
||||
const b64 = base64_encode(arg);
|
||||
log("[v5shoutbox] AUTHENTICATE command sent (not shown)");
|
||||
conn.socket.send("AUTHENTICATE " + b64);
|
||||
}
|
||||
|
||||
/* Default authentication if SASL is unavailable or fails */
|
||||
if(msg.command === "NOTICE" && msg.args[1].includes("/AUTH")) {
|
||||
log("[v5shoutbox] AUTH command sent (not shown)");
|
||||
conn.socket.send("AUTH " + conn.username + ":" + conn.password);
|
||||
}
|
||||
|
||||
if(msg.command === 900) {
|
||||
log("[v5shoutbox] Authenticated.");
|
||||
if(conn.caps.includes("sasl"))
|
||||
sendRaw("CAP END");
|
||||
setState(State.READY);
|
||||
conn.password = undefined;
|
||||
irc.onAuthenticated();
|
||||
|
||||
if(conn.caps.includes("draft/chathistory")) {
|
||||
["#annonces", "#projets", "#hs"].forEach(channel => {
|
||||
send("CHATHISTORY", ["LATEST", channel, "*", "100"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(msg.command === "JOIN" && msg.tags.get("account") === conn.username) {
|
||||
irc.channels.set(msg.args[0], {});
|
||||
irc.onChannelChanged(msg.args[0]);
|
||||
}
|
||||
if(msg.command === 332) {
|
||||
irc.channels.get(msg.args[1]).header = msg.args[2];
|
||||
irc.onChannelChanged(msg.args[1]);
|
||||
}
|
||||
if(msg.command === 353 && msg.args[1] === "=") {
|
||||
irc.channels.get(msg.args[2]).clients = msg.args[3].split(" ");
|
||||
irc.onChannelChanged(msg.args[2]);
|
||||
}
|
||||
|
||||
if(msg.command === "PRIVMSG")
|
||||
processPRIVMSG(msg);
|
||||
}
|
||||
|
||||
function processBatch(batch) {
|
||||
const batchArgs = batch[0].args.slice(1);
|
||||
if(batchArgs.length === 2 && batchArgs[0] === "chathistory") {
|
||||
batch.forEach(msg => {
|
||||
if(msg.command === "PRIVMSG")
|
||||
processPRIVMSG(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function processPRIVMSG(msg) {
|
||||
if(msg.args.length == 2) {
|
||||
let source = msg.source;
|
||||
if(source.includes("!"))
|
||||
source = source.substr(0, source.indexOf("!"));
|
||||
let date = new Date();
|
||||
if(msg.tags.has("time"))
|
||||
date = new Date(msg.tags.get("time"));
|
||||
irc.onNewMessage(msg.args[0], date, source, msg.args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
this.postMessage = function(channel, message) {
|
||||
if(message.startsWith("/")) {
|
||||
sendRaw(message.substring(1));
|
||||
}
|
||||
else {
|
||||
send("PRIVMSG", [channel, message]);
|
||||
if(!conn.caps.includes("echo-message"))
|
||||
irc.onNewMessage(channel, new Date(), conn.username, message);
|
||||
}
|
||||
console.log(registration);
|
||||
} catch (error) {
|
||||
console.error(`Registration failed with ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
registerServiceWorker();
|
||||
|
||||
let irc = new IRC.Client("wss://irc.planet-casio.com:443", undefined);
|
||||
|
||||
/* Shoutbox entry point and DOM manipulation. */
|
||||
let shoutbox = new function() {
|
||||
|
||||
const serverUrl = "wss://irc.planet-casio.com:443";
|
||||
const postUrl = undefined;
|
||||
const availableChannels = ["#annonces", "#projets", "#hs"];
|
||||
|
||||
/* Original tab title */
|
||||
|
@ -377,7 +99,7 @@ let shoutbox = new function() {
|
|||
|
||||
this.eLoginForm.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
shoutbox.connect(serverUrl, postUrl);
|
||||
shoutbox.connect();
|
||||
});
|
||||
this.eShoutboxForm.addEventListener("submit", e => {
|
||||
e.preventDefault();
|
||||
|
@ -543,7 +265,7 @@ let shoutbox = new function() {
|
|||
|
||||
/*** User interactions ***/
|
||||
|
||||
this.connect = function(serverUrl, postUrl) {
|
||||
this.connect = function() {
|
||||
if(irc.isConnected())
|
||||
return this.log("Already connected!");
|
||||
if(this.eLogin.value === "" || this.ePassword.value === "")
|
||||
|
@ -551,7 +273,7 @@ let shoutbox = new function() {
|
|||
if(this.eLogin.value.includes(" "))
|
||||
return this.log("Login should not contain a space!");
|
||||
|
||||
irc.connect(serverUrl, postUrl, this.eLogin.value, this.ePassword.value);
|
||||
irc.connect(this.eLogin.value, this.ePassword.value);
|
||||
}
|
||||
|
||||
this.selectChannel = function(name) {
|
||||
|
|
|
@ -0,0 +1,307 @@
|
|||
/* WebSocket IRC with HTTP-based website interface. */
|
||||
|
||||
class Message {
|
||||
/* Regex for parsing messages */
|
||||
static #rMessage = (function(){
|
||||
const rTags = /(?:@([^ ]+) +)?/;
|
||||
const rSource = /(?::([^ ]+) +)?/;
|
||||
const rCommand = /(?:([a-zA-Z]+)|(\d{3}))/;
|
||||
const rArgs = /((?: +[^\r\n :][^\r\n ]*)*)/;
|
||||
const rTrailing = /(?: +:([^\r\n]*))?/;
|
||||
return new RegExp([/^/, rTags, rSource, rCommand, rArgs, rTrailing, /$/]
|
||||
.map(r => r.source).join(""));
|
||||
})();
|
||||
|
||||
constructor(text) {
|
||||
const matches = Message.#rMessage.exec(text);
|
||||
if(matches === undefined)
|
||||
return;
|
||||
|
||||
this.tags = new Map();
|
||||
(matches[1] || "").split(";").filter(s => s).forEach(s => {
|
||||
const parts = s.split(/=(.*)/);
|
||||
const value = (parts[1] || "").replaceAll("\\\\", "\\")
|
||||
.replaceAll("\\:", ";")
|
||||
.replaceAll("\\r", "\r")
|
||||
.replaceAll("\\n", "\n")
|
||||
.replaceAll(/\\./g, m => m[1]);
|
||||
this.tags.set(parts[0], value);
|
||||
});
|
||||
|
||||
this.source = matches[2];
|
||||
this.command = matches[3] || parseInt(matches[4]);
|
||||
this.args = (matches[5] || "").split(" ").filter(s => s);
|
||||
if(matches[6] !== undefined)
|
||||
this.args.push(matches[6]);
|
||||
}
|
||||
}
|
||||
|
||||
const State = Object.freeze({
|
||||
/* Websocket is disconnected. */
|
||||
DISCONNECTED: Symbol("DISCONNECTED"),
|
||||
/* We just opened a socket to the IRC server and have yet to register. */
|
||||
CONNECTING: Symbol("CONNECTING"),
|
||||
/* We are authenticated and ready to use the server. */
|
||||
READY: Symbol("READY"),
|
||||
});
|
||||
|
||||
/* https://stackoverflow.com/questions/30106476 */
|
||||
function base64_encode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||
function toSolidBytes(match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
export class Client {
|
||||
#ws;
|
||||
#password;
|
||||
|
||||
constructor(serverUrl, postUrl) {
|
||||
/* IRC server's websocket URL (wss://...). */
|
||||
this.serverUrl = serverUrl;
|
||||
/* HTTP-based message post URL. Undefined when using pure IRC, or a v4/v5
|
||||
URL when using website authentication. */
|
||||
this.postUrl = postUrl;
|
||||
/* Websocket for IRC server communication. */
|
||||
this.#ws = undefined;
|
||||
/* Current connection state. */
|
||||
this.state = State.DISCONNECTED;
|
||||
/* Capabilities enabled. */
|
||||
this.caps = [];
|
||||
/* List of channels and their information */
|
||||
this.channels = new Map();
|
||||
/* Current channel. This remains defined even when DISCONNECTED. */
|
||||
this.channel = undefined;
|
||||
/* Username with which we are logged in. Undefined when DISCONNECTED. */
|
||||
this.username = undefined;
|
||||
/* Password for connection. Erased after authenticating. */
|
||||
this.#password = undefined;
|
||||
/* Batches currently being processed */
|
||||
this.batches = new Map();
|
||||
}
|
||||
|
||||
/*** Accessors/mutators ***/
|
||||
|
||||
isConnected() {
|
||||
return this.state !== State.DISCONNECTED;
|
||||
}
|
||||
isRunning() {
|
||||
return this.state === State.READY;
|
||||
}
|
||||
stateString() {
|
||||
switch(this.state) {
|
||||
case State.DISCONNECTED: return "Disconnected";
|
||||
case State.CONNECTED: return "Connecting...";
|
||||
case State.READY: return "Connected";
|
||||
}
|
||||
}
|
||||
|
||||
currentChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
selectChannel(name) {
|
||||
this.channel = name;
|
||||
}
|
||||
isOnRemoteChannel() {
|
||||
const c = this.channel;
|
||||
return (c !== undefined) && (c.startsWith("&") || c.startsWith("#"));
|
||||
}
|
||||
|
||||
/*** Overridable hooks and callbacks ***/
|
||||
|
||||
onLog(message) {}
|
||||
onStateChanged() {}
|
||||
onAuthenticated() {}
|
||||
onNewMessage(channel, date, author, message) {}
|
||||
onChannelChanged(channel) {}
|
||||
|
||||
log(...args) {
|
||||
this.onLog(...args);
|
||||
}
|
||||
|
||||
/*** Connection management ***/
|
||||
|
||||
setState(state) {
|
||||
this.state = state;
|
||||
this.onStateChanged();
|
||||
}
|
||||
|
||||
connect(username, password) {
|
||||
let ws = new WebSocket(this.serverUrl);
|
||||
this.#ws = ws;
|
||||
this.log("[v5shoutbox] Connecting...");
|
||||
|
||||
ws.onopen = function() {
|
||||
this.log("[v5shoutbox] Connected.");
|
||||
this.setState(State.CONNECTED);
|
||||
this.username = username;
|
||||
this.#password = password;
|
||||
this.#startExchange();
|
||||
}.bind(this);
|
||||
|
||||
ws.onmessage = function(net) {
|
||||
this.log("[<] " + net.data);
|
||||
let msg = new Message(net.data);
|
||||
if(msg.command === undefined) {
|
||||
this.log("[v5shoutbox] invalid message");
|
||||
return;
|
||||
}
|
||||
this.processMessage(msg);
|
||||
}.bind(this);
|
||||
|
||||
ws.onclose = function() {
|
||||
this.log("[v5shoutbox] Disconnected.");
|
||||
this.setState(State.DISCONNECTED);
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
#sendRaw(command) {
|
||||
this.log("[>] " + command);
|
||||
this.#ws.send(command);
|
||||
}
|
||||
|
||||
send(command, args) {
|
||||
if(args.slice(0,-1).some(x => x.includes(" ")))
|
||||
return this.log("invalid spaces in non-last args: " + command.toString() +
|
||||
" " + args.toString());
|
||||
|
||||
if(args.length && args[args.length - 1].includes(" "))
|
||||
args[args.length - 1] = ":" + args[args.length - 1];
|
||||
|
||||
this.#sendRaw(command + " " + args.join(" "));
|
||||
}
|
||||
|
||||
/*** IRC protocol ***/
|
||||
|
||||
#startExchange() {
|
||||
this.#sendRaw("CAP LS 302");
|
||||
this.send("NICK", [this.username]);
|
||||
this.send("USER", [this.username, "*", "0", this.username]);
|
||||
}
|
||||
|
||||
processMessage(msg) {
|
||||
if(msg.command === "PING") {
|
||||
this.send("PONG", msg.args);
|
||||
return;
|
||||
}
|
||||
if(msg.command === "BATCH") {
|
||||
const [mode, id] = [msg.args[0][0], msg.args[0].substring(1)];
|
||||
if(mode == "+")
|
||||
this.batches.set(id, []);
|
||||
this.batches.get(id).push(msg);
|
||||
if(mode == "-") {
|
||||
this.processBatch(this.batches.get(id));
|
||||
this.batches.delete(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(msg.tags.has("batch")) {
|
||||
this.batches.get(msg.tags.get("batch")).push(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(msg.command === "CAP" && msg.args[1] === "LS") {
|
||||
const caps = msg.args[2].split(" ").filter(x => x);
|
||||
const wishlist =
|
||||
["message-tags", "server-time", "echo-message",
|
||||
"batch", "sasl", "draft/chathistory"];
|
||||
|
||||
let req = wishlist.filter(x => caps.includes(x));
|
||||
if(caps.some(x => x.startsWith("sasl=")))
|
||||
req.push("sasl");
|
||||
|
||||
if(req.length)
|
||||
this.send("CAP", ["REQ", req.join(" ")]);
|
||||
else {
|
||||
this.caps = [];
|
||||
this.#sendRaw("CAP END");
|
||||
}
|
||||
}
|
||||
|
||||
if(msg.command === "CAP" && msg.args[1] === "ACK") {
|
||||
this.caps = msg.args[2].split(" ").filter(x => x);
|
||||
if(this.caps.includes("sasl"))
|
||||
this.#sendRaw("AUTHENTICATE PLAIN");
|
||||
else
|
||||
this.#sendRaw("CAP END");
|
||||
}
|
||||
if(msg.command === "AUTHENTICATE" && msg.args[0] === "+") {
|
||||
const arg = this.username + "\0" + this.username + "\0" + this.#password;
|
||||
const b64 = base64_encode(arg);
|
||||
this.log("[v5shoutbox] AUTHENTICATE command sent (not shown)");
|
||||
this.#ws.send("AUTHENTICATE " + b64);
|
||||
}
|
||||
|
||||
/* Default authentication if SASL is unavailable or fails */
|
||||
if(msg.command === "NOTICE" && msg.args[1].includes("/AUTH")) {
|
||||
this.log("[v5shoutbox] AUTH command sent (not shown)");
|
||||
this.#ws.send("AUTH " + this.username + ":" + this.#password);
|
||||
}
|
||||
|
||||
if(msg.command === 900) {
|
||||
this.log("[v5shoutbox] Authenticated.");
|
||||
if(this.caps.includes("sasl"))
|
||||
this.#sendRaw("CAP END");
|
||||
this.setState(State.READY);
|
||||
this.#password = undefined;
|
||||
this.onAuthenticated();
|
||||
|
||||
if(this.caps.includes("draft/chathistory")) {
|
||||
["#annonces", "#projets", "#hs"].forEach(channel => {
|
||||
this.send("CHATHISTORY", ["LATEST", channel, "*", "100"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(msg.command === "JOIN"
|
||||
&& msg.tags.get("account") === this.username) {
|
||||
this.channels.set(msg.args[0], {});
|
||||
this.onChannelChanged(msg.args[0]);
|
||||
}
|
||||
if(msg.command === 332) {
|
||||
this.channels.get(msg.args[1]).header = msg.args[2];
|
||||
this.onChannelChanged(msg.args[1]);
|
||||
}
|
||||
if(msg.command === 353 && msg.args[1] === "=") {
|
||||
this.channels.get(msg.args[2]).clients = msg.args[3].split(" ");
|
||||
this.onChannelChanged(msg.args[2]);
|
||||
}
|
||||
|
||||
if(msg.command === "PRIVMSG")
|
||||
this.processPRIVMSG(msg);
|
||||
}
|
||||
|
||||
processBatch(batch) {
|
||||
const batchArgs = batch[0].args.slice(1);
|
||||
if(batchArgs.length === 2 && batchArgs[0] === "chathistory") {
|
||||
batch.forEach(msg => {
|
||||
if(msg.command === "PRIVMSG")
|
||||
this.processPRIVMSG(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
processPRIVMSG(msg) {
|
||||
if(msg.args.length == 2) {
|
||||
let source = msg.source;
|
||||
if(source.includes("!"))
|
||||
source = source.substr(0, source.indexOf("!"));
|
||||
let date = new Date();
|
||||
if(msg.tags.has("time"))
|
||||
date = new Date(msg.tags.get("time"));
|
||||
this.onNewMessage(msg.args[0], date, source, msg.args[1]);
|
||||
}
|
||||
}
|
||||
|
||||
postMessage(channel, message) {
|
||||
if(message.startsWith("/")) {
|
||||
this.#sendRaw(message.substring(1));
|
||||
}
|
||||
else {
|
||||
this.send("PRIVMSG", [channel, message]);
|
||||
if(!this.caps.includes("echo-message"))
|
||||
this.onNewMessage(channel, new Date(), conn.username, message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
self.addEventListener("activate", (e) => {
|
||||
console.log("[worker] activating!");
|
||||
});
|
||||
|
||||
self.addEventListener("install", (e) => {
|
||||
console.log("[worker] installing!");
|
||||
});
|
||||
|
||||
self.addEventListener("message", (e) => {
|
||||
console.log("[worker] message!");
|
||||
console.log(e);
|
||||
console.log(self);
|
||||
// if(e.data[0] == "!")
|
||||
// postMessage(e.data.substring(1));
|
||||
});
|
Loading…
Reference in New Issue