mirror of
https://git.sr.ht/~emersion/gamja
synced 2024-11-28 18:45:51 -05:00
lib/irc: add CapRegistry
This commit is contained in:
parent
f6895fed32
commit
4cabae89ff
5 changed files with 90 additions and 59 deletions
|
@ -322,10 +322,11 @@ export default {
|
|||
execute: (app, args) => {
|
||||
let newRealname = args.join(" ");
|
||||
let client = getActiveClient(app);
|
||||
if (!client.enabledCaps["setname"]) {
|
||||
if (!client.caps.enabled.has("setname")) {
|
||||
throw new Error("Server doesn't support changing the realname");
|
||||
}
|
||||
client.send({ command: "SETNAME", params: [newRealname] });
|
||||
// TODO: save to local storage
|
||||
},
|
||||
},
|
||||
"stats": {
|
||||
|
|
|
@ -364,7 +364,7 @@ export default class App extends Component {
|
|||
let client = this.clients.get(serverID);
|
||||
|
||||
let stored = this.bufferStore.get({ name, server: client.params });
|
||||
if (client.enabledCaps["draft/chathistory"] && stored) {
|
||||
if (client.caps.enabled.has("draft/chathistory") && stored) {
|
||||
this.setBufferState({ server: serverID, name }, { unread: stored.unread });
|
||||
}
|
||||
if (!stored) {
|
||||
|
@ -855,7 +855,7 @@ export default class App extends Component {
|
|||
switch (msg.command) {
|
||||
case irc.RPL_WELCOME:
|
||||
let lastReceipt = this.latestReceipt(ReceiptType.DELIVERED);
|
||||
if (lastReceipt && lastReceipt.time && client.enabledCaps["draft/chathistory"] && (!client.enabledCaps["soju.im/bouncer-networks"] || client.params.bouncerNetwork)) {
|
||||
if (lastReceipt && lastReceipt.time && client.caps.enabled.has("draft/chathistory") && (!client.caps.enabled.has("soju.im/bouncer-networks") || client.params.bouncerNetwork)) {
|
||||
let now = irc.formatDate(new Date());
|
||||
client.fetchHistoryTargets(now, lastReceipt.time).then((targets) => {
|
||||
targets.forEach((target) => {
|
||||
|
@ -878,7 +878,7 @@ export default class App extends Component {
|
|||
}
|
||||
|
||||
if (client.isChannel(buf.name)) {
|
||||
if (client.enabledCaps["soju.im/bouncer-networks"]) {
|
||||
if (client.caps.enabled.has("soju.im/bouncer-networks")) {
|
||||
continue;
|
||||
}
|
||||
join.push(buf.name);
|
||||
|
@ -1068,7 +1068,7 @@ export default class App extends Component {
|
|||
if (!bouncerNetID) {
|
||||
// Open dialog to create network if bouncer
|
||||
let client = this.clients.values().next().value;
|
||||
if (!client || !client.enabledCaps["soju.im/bouncer-networks"]) {
|
||||
if (!client || !client.caps.enabled.has("soju.im/bouncer-networks")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1171,7 +1171,7 @@ export default class App extends Component {
|
|||
return { buffers, activeBuffer };
|
||||
});
|
||||
|
||||
let disconnectAll = client && !client.params.bouncerNetwork && client.enabledCaps["soju.im/bouncer-networks"];
|
||||
let disconnectAll = client && !client.params.bouncerNetwork && client.caps.enabled.has("soju.im/bouncer-networks");
|
||||
|
||||
this.disconnect(buf.server);
|
||||
|
||||
|
@ -1255,7 +1255,7 @@ export default class App extends Component {
|
|||
let msg = { command: "PRIVMSG", params: [target, text] };
|
||||
client.send(msg);
|
||||
|
||||
if (!client.enabledCaps["echo-message"]) {
|
||||
if (!client.caps.enabled.has("echo-message")) {
|
||||
msg.prefix = { name: client.nick };
|
||||
this.addMessage(serverID, target, msg);
|
||||
}
|
||||
|
@ -1395,7 +1395,7 @@ export default class App extends Component {
|
|||
|
||||
let client = this.clients.get(buf.server);
|
||||
|
||||
if (!client || !client.enabledCaps["draft/chathistory"] || !client.enabledCaps["server-time"]) {
|
||||
if (!client || !client.caps.enabled.has("draft/chathistory") || !client.caps.enabled.has("server-time")) {
|
||||
return;
|
||||
}
|
||||
if (this.endOfHistory.get(buf.id)) {
|
||||
|
@ -1413,7 +1413,7 @@ export default class App extends Component {
|
|||
this.endOfHistory.set(buf.id, true);
|
||||
|
||||
let limit = 100;
|
||||
if (client.enabledCaps["draft/event-playback"]) {
|
||||
if (client.caps.enabled.has("draft/event-playback")) {
|
||||
limit = 200;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,8 +109,7 @@ export default class Client extends EventTarget {
|
|||
serverPrefix = { name: "*" };
|
||||
nick = null;
|
||||
supportsCap = false;
|
||||
availableCaps = {};
|
||||
enabledCaps = {};
|
||||
caps = new irc.CapRegistry();
|
||||
isupport = new irc.Isupport();
|
||||
|
||||
ws = null;
|
||||
|
@ -187,8 +186,7 @@ export default class Client extends EventTarget {
|
|||
this.setStatus(Client.Status.DISCONNECTED);
|
||||
this.nick = null;
|
||||
this.serverPrefix = null;
|
||||
this.availableCaps = {};
|
||||
this.enabledCaps = {};
|
||||
this.caps = new irc.CapRegistry();
|
||||
this.batches = new Map();
|
||||
Object.keys(this.pendingCmds).forEach((k) => {
|
||||
this.pendingCmds[k] = Promise.resolve(null);
|
||||
|
@ -602,21 +600,8 @@ export default class Client extends EventTarget {
|
|||
});
|
||||
}
|
||||
|
||||
addAvailableCaps(s) {
|
||||
let l = s.split(" ");
|
||||
l.forEach((s) => {
|
||||
let i = s.indexOf("=");
|
||||
let k = s, v = "";
|
||||
if (i >= 0) {
|
||||
k = s.slice(0, i);
|
||||
v = s.slice(i + 1);
|
||||
}
|
||||
this.availableCaps[k.toLowerCase()] = v;
|
||||
});
|
||||
}
|
||||
|
||||
supportsSASL(mech) {
|
||||
let saslCap = this.availableCaps["sasl"];
|
||||
let saslCap = this.caps.available.get("sasl");
|
||||
if (saslCap === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
@ -624,7 +609,7 @@ export default class Client extends EventTarget {
|
|||
}
|
||||
|
||||
checkAccountRegistrationCap(k) {
|
||||
let v = this.availableCaps["draft/account-registration"];
|
||||
let v = this.caps.available.get("draft/account-registration");
|
||||
if (v === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
@ -637,35 +622,30 @@ export default class Client extends EventTarget {
|
|||
wantCaps.push("soju.im/bouncer-networks-notify");
|
||||
}
|
||||
|
||||
let reqCaps = [];
|
||||
wantCaps.forEach((cap) => {
|
||||
if (this.availableCaps[cap] !== undefined && !this.enabledCaps[cap]) {
|
||||
reqCaps.push(cap);
|
||||
}
|
||||
});
|
||||
|
||||
if (reqCaps.length > 0) {
|
||||
this.send({ command: "CAP", params: ["REQ", reqCaps.join(" ")] });
|
||||
let msg = this.caps.requestAvailable(wantCaps);
|
||||
if (msg) {
|
||||
this.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
handleCap(msg) {
|
||||
this.caps.parse(msg);
|
||||
|
||||
let subCmd = msg.params[1];
|
||||
let args = msg.params.slice(2);
|
||||
switch (subCmd) {
|
||||
case "LS":
|
||||
this.supportsCap = true;
|
||||
this.addAvailableCaps(args[args.length - 1]);
|
||||
if (args[0] == "*") {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log("Available server caps:", this.availableCaps);
|
||||
console.log("Available server caps:", this.caps.available);
|
||||
|
||||
this.requestCaps();
|
||||
|
||||
if (this.status !== Client.Status.REGISTERED) {
|
||||
if (this.availableCaps["sasl"] !== undefined) {
|
||||
if (this.caps.available.has("sasl")) {
|
||||
let promise;
|
||||
if (this.params.saslPlain) {
|
||||
promise = this.authenticate("PLAIN", this.params.saslPlain);
|
||||
|
@ -678,7 +658,7 @@ export default class Client extends EventTarget {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.availableCaps["soju.im/bouncer-networks"] !== undefined && this.params.bouncerNetwork) {
|
||||
if (this.caps.available.has("soju.im/bouncer-networks") && this.params.bouncerNetwork) {
|
||||
this.send({ command: "BOUNCER", params: ["BIND", this.params.bouncerNetwork] });
|
||||
}
|
||||
|
||||
|
@ -686,28 +666,18 @@ export default class Client extends EventTarget {
|
|||
}
|
||||
break;
|
||||
case "NEW":
|
||||
this.addAvailableCaps(args[0]);
|
||||
console.log("Server added available caps:", args[0]);
|
||||
this.requestCaps();
|
||||
break;
|
||||
case "DEL":
|
||||
args[0].split(" ").forEach((cap) => {
|
||||
cap = cap.toLowerCase();
|
||||
delete this.availableCaps[cap];
|
||||
delete this.enabledCaps[cap];
|
||||
});
|
||||
console.log("Server removed available caps:", args[0]);
|
||||
break;
|
||||
case "ACK":
|
||||
console.log("Server ack'ed caps:", args[0]);
|
||||
args[0].split(" ").forEach((cap) => {
|
||||
cap = cap.toLowerCase();
|
||||
this.enabledCaps[cap] = true;
|
||||
});
|
||||
break;
|
||||
case "NAK":
|
||||
console.log("Server nak'ed caps:", args[0]);
|
||||
if (this.status != Client.Status.REGISTERED) {
|
||||
if (this.status !== Client.Status.REGISTERED) {
|
||||
this.send({ command: "CAP", params: ["END"] });
|
||||
}
|
||||
break;
|
||||
|
@ -764,7 +734,7 @@ export default class Client extends EventTarget {
|
|||
let cmd = msg.command;
|
||||
|
||||
let label;
|
||||
if (this.enabledCaps["labeled-response"]) {
|
||||
if (this.caps.enabled.has("labeled-response")) {
|
||||
lastLabel++;
|
||||
label = String(lastLabel);
|
||||
msg.tags = { ...msg.tags, label };
|
||||
|
@ -950,10 +920,6 @@ export default class Client extends EventTarget {
|
|||
}
|
||||
|
||||
listBouncerNetworks() {
|
||||
if (!this.enabledCaps["soju.im/bouncer-networks"]) {
|
||||
return Promise.reject(new Error("Server doesn't support the BOUNCER extension"));
|
||||
}
|
||||
|
||||
let req = { command: "BOUNCER", params: ["LISTNETWORKS"] };
|
||||
return this.fetchBatch(req, "soju.im/bouncer-networks").then((batch) => {
|
||||
let networks = new Map();
|
||||
|
|
64
lib/irc.js
64
lib/irc.js
|
@ -794,3 +794,67 @@ export function parseURL(str) {
|
|||
|
||||
return { host, enttype, entity };
|
||||
}
|
||||
|
||||
export class CapRegistry {
|
||||
available = new Map();
|
||||
enabled = new Set();
|
||||
|
||||
addAvailable(s) {
|
||||
let l = s.split(" ");
|
||||
l.forEach((s) => {
|
||||
let i = s.indexOf("=");
|
||||
let k = s, v = "";
|
||||
if (i >= 0) {
|
||||
k = s.slice(0, i);
|
||||
v = s.slice(i + 1);
|
||||
}
|
||||
this.available.set(k.toLowerCase(), v);
|
||||
});
|
||||
}
|
||||
|
||||
parse(msg) {
|
||||
if (msg.command !== "CAP") {
|
||||
return;
|
||||
}
|
||||
|
||||
let subCmd = msg.params[1];
|
||||
let args = msg.params.slice(2);
|
||||
switch (subCmd) {
|
||||
case "LS":
|
||||
this.addAvailable(args[args.length - 1]);
|
||||
break;
|
||||
case "NEW":
|
||||
this.addAvailable(args[0]);
|
||||
break;
|
||||
case "DEL":
|
||||
args[0].split(" ").forEach((cap) => {
|
||||
cap = cap.toLowerCase();
|
||||
this.available.delete(cap);
|
||||
this.enabled.delete(cap);
|
||||
});
|
||||
break;
|
||||
case "ACK":
|
||||
// TODO: handle `ACK -cap` to
|
||||
args[0].split(" ").forEach((cap) => {
|
||||
cap = cap.toLowerCase();
|
||||
if (cap.startsWith("-")) {
|
||||
this.enabled.delete(cap.slice(1));
|
||||
} else {
|
||||
this.enabled.add(cap);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requestAvailable(l) {
|
||||
l = l.filter((cap) => {
|
||||
return this.available.has(cap) && !this.enabled.has(cap);
|
||||
});
|
||||
|
||||
if (l.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return { command: "CAP", params: ["REQ", l.join(" ")] };
|
||||
}
|
||||
}
|
||||
|
|
4
state.js
4
state.js
|
@ -360,8 +360,8 @@ export const State = {
|
|||
case "CAP":
|
||||
return updateServer({
|
||||
supportsSASLPlain: client.supportsSASL("PLAIN"),
|
||||
supportsAccountRegistration: !!client.enabledCaps["draft/account-registration"],
|
||||
isBouncer: !!client.enabledCaps["soju.im/bouncer-networks"],
|
||||
supportsAccountRegistration: client.caps.enabled.has("draft/account-registration"),
|
||||
isBouncer: client.caps.enabled.has("soju.im/bouncer-networks"),
|
||||
});
|
||||
case irc.RPL_LOGGEDIN:
|
||||
return updateServer({ account: msg.params[2] });
|
||||
|
|
Loading…
Reference in a new issue