implement labeled-response

This commit is contained in:
emerson 2022-06-02 07:21:11 -04:00
parent b1677bb6ad
commit c8a0a7dd6a

View file

@ -51,27 +51,27 @@ export abstract class Client {
});
}
getMatrixUserFromNick(targetNick: string) {
getMatrixUserFromNick(targetNick: string, message: IRCMessage) {
const target = this.server.nickToMatrixUser.get(targetNick);
if (!target) {
this.sendMessage(this.server.name, "401", [this.user.nick, targetNick, "No such nick"]);
this.sendMessage(this.server.name, "401", [this.user.nick, targetNick, "No such nick"], message.tags);
return false;
}
return target;
}
getChannel(channel: string) {
getChannel(channel: string, message: IRCMessage) {
const targetChannel = this.server.channels.get(channel);
if (!targetChannel) {
this.sendMessage(this.server.name, "403", [this.user.nick, channel, "No such channel"]);
this.sendMessage(this.server.name, "403", [this.user.nick, channel, "No such channel"], message.tags);
return false;
}
return targetChannel;
}
checkIfInChannel(channel: Channel) {
checkIfInChannel(channel: Channel, message: IRCMessage) {
if (!this.server.channels.get(channel.name)) {
this.sendMessage(this.server.name, "442", [this.user.nick, "You're not on that channel"]);
this.sendMessage(this.server.name, "442", [this.user.nick, "You're not on that channel"], message.tags);
return false;
}
return true;
@ -79,7 +79,7 @@ export abstract class Client {
checkMinParams(message: IRCMessage, neededNumber: number) {
if (message.params.length < neededNumber) {
this.sendMessage(this.server.name, "461", [this.user.nick, message.command, "Not enough parameters"]);
this.sendMessage(this.server.name, "461", [this.user.nick, message.command, "Not enough parameters"], message.tags);
return false;
}
return true;
@ -112,6 +112,9 @@ export abstract class Client {
case 'AUTHENTICATE':
this.doAUTHENTICATE(message);
break;
case 'BATCH':
this.doBATCH(message);
break;
case 'CAP':
this.doCAP(message);
break;
@ -151,6 +154,18 @@ export abstract class Client {
case 'WHO':
this.doWHO(message);
break;
case 'WHOIS':
this.doWHOIS(message);
break;
case 'JOIN':
case 'NICK':
case 'USER':
// Exempting these from sending a 421, otherwise it will get annoying
break;
default:
this.sendMessage(this.server.name, "421", [message.command, 'Unknown command'], message.tags);
console.log(`unknown command ${message.command}`);
break;
}
}
@ -161,12 +176,12 @@ export abstract class Client {
else {
const authArray = Buffer.from(message.params[0], 'base64').toString('utf-8').split('\0');
if (!authArray || authArray.length !== 3) {
this.sendMessage(this.server.name, '904', ['*', "SASL Authentication failed"])
this.sendMessage(this.server.name, '904', ['*', "SASL Authentication failed"], message.tags)
this.closeConnectionWithError('Invalid authentication')
}
if (authArray[2] === this.server.config.SASLPassword) {
this.sendMessage(this.server.name, '900', [this.user.nick, this.server.getMask(), this.user.accountName, `You are now logged in as ${this.user.nick}`]);
this.sendMessage(this.server.name, '903', [this.user.nick, "SASL authentication successful"]);
this.sendMessage(this.server.name, '900', [this.user.nick, this.server.getMask(), this.user.accountName, `You are now logged in as ${this.user.nick}`], message.tags);
this.sendMessage(this.server.name, '903', [this.user.nick, "SASL authentication successful"], message.tags);
this.isRegistered = true;
}
}
@ -198,11 +213,11 @@ export abstract class Client {
if (message.params.length === 2) {
this.capVersion = message.params[1];
}
this.sendMessage(this.server.name, "CAP", ["*", "LS", this.getCapString(this.capVersion)]);
this.sendMessage(this.server.name, "CAP", ["*", "LS", this.getCapString(this.capVersion)], message.tags);
break;
}
case 'LIST': {
this.sendMessage(this.server.name, "CAP", ["*", "LIST", this.getCapString(this.capVersion)]);
this.sendMessage(this.server.name, "CAP", ["*", "LIST", this.getCapString(this.capVersion)], message.tags);
break;
}
case 'REQ': {
@ -214,7 +229,7 @@ export abstract class Client {
capsEnabled.push(cap);
}
});
this.sendMessage(this.server.name, "CAP", ["*", "ACK", capsEnabled.join(' ')]);
this.sendMessage(this.server.name, "CAP", ["*", "ACK", capsEnabled.join(' ')], message.tags);
break;
}
case 'END': {
@ -231,10 +246,10 @@ export abstract class Client {
doDELETEMSG(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
const eventId = message.tags.get("reflectionircd.chat/delete-message");
if (!this.user || !targetChannel || !eventId) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
const data = {
"reason": (message.params.length === 2) ? message.params[1] : ""
}
@ -263,10 +278,10 @@ export abstract class Client {
doINVITE(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 2))
return;
const targetUser = this.getMatrixUserFromNick(message.params[0]);
const targetChannel = this.getChannel(message.params[1]);
const targetUser = this.getMatrixUserFromNick(message.params[0], message);
const targetChannel = this.getChannel(message.params[1], message);
if (!this.user || !targetUser || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (targetChannel.matrixUsers.has(targetUser.nick)) {
this.sendMessage(this.server.name, "443", [this.user.nick, targetUser.nick, "is already on channel"]);
return;
@ -300,10 +315,10 @@ export abstract class Client {
doKICK(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 2))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetUser = this.getMatrixUserFromNick(message.params[1]);
const targetChannel = this.getChannel(message.params[0], message);
const targetUser = this.getMatrixUserFromNick(message.params[1], message);
if (!this.user || !targetUser || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (!targetChannel.matrixUsers.has(targetUser.nick)) {
this.sendMessage(this.server.name, "441", [this.user.nick, targetUser.nick, targetChannel.name, "They aren't on that channel"]);
return;
@ -340,16 +355,16 @@ export abstract class Client {
const targetChannel = this.server.channels.get(message.params[0]);
if (!targetChannel) {
if (message.params[0] !== this.user.nick) {
this.sendMessage(this.server.name, "502", [this.user.nick, "Can't view mode for other users"]);
this.sendMessage(this.server.name, "502", [this.user.nick, "Can't view mode for other users"], message.tags);
return;
}
this.sendMessage(this.server.name, "221", [this.user.nick, "+i"]);
return;
}
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (message.params.length === 1) {
const chanModes = [...targetChannel.channelModes.keys()].sort().join('');
this.sendMessage(this.server.name, "324", [this.user.nick, targetChannel.name, `+${chanModes}`]);
this.sendMessage(this.server.name, "324", [this.user.nick, targetChannel.name, `+${chanModes}`], message.tags);
return;
}
}
@ -361,9 +376,9 @@ export abstract class Client {
doMSG(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 2))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (targetChannel.roomType === "m.space") {
this.sendMessage(this.server.name, "NOTICE", [targetChannel.name, "Sending messages to spaces is not allowed"], new Map());
return;
@ -419,8 +434,12 @@ export abstract class Client {
});
}
sendNAMES(targetChannel: Channel) {
sendNAMES(targetChannel: Channel, batchLabel: string = "") {
if (!this.user) return;
const newTag = new Map();
if (batchLabel) {
newTag.set('batch', batchLabel);
}
let namesList: string[] = [];
for (const matrixUser of targetChannel.matrixUsers.values()) {
const opStatus = targetChannel.getNickPowerLevelMapping(matrixUser.nick);
@ -432,31 +451,40 @@ export abstract class Client {
singleNamesList.push(singleName);
}
else {
this.sendMessage(this.server.name, "353", [this.user.nick, "=", targetChannel.name, `${singleNamesList.join(' ')}`]);
this.sendMessage(this.server.name, "353", [this.user.nick, "=", targetChannel.name, `${singleNamesList.join(' ')}`], newTag);
singleNamesList = [];
}
})
if (singleNamesList.length !== 0) {
this.sendMessage(this.server.name, "353", [this.user.nick, "=", targetChannel.name, `${singleNamesList.join(' ')}`]);
this.sendMessage(this.server.name, "353", [this.user.nick, "=", targetChannel.name, `${singleNamesList.join(' ')}`], newTag);
}
this.sendMessage(this.server.name, "366", [this.user.nick, targetChannel.name, "End of /NAMES list"]);
this.sendMessage(this.server.name, "366", [this.user.nick, targetChannel.name, "End of /NAMES list"], newTag);
}
doNAMES(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
this.sendNAMES(targetChannel);
if (!this.checkIfInChannel(targetChannel, message)) return;
const messageLabel = message.tags.get('label') || "";
let batchLabel = "";
if (messageLabel) {
batchLabel = Math.random().toString(36).substring(2,7);
this.sendMessage(this.server.name, 'BATCH', [`+${batchLabel}`, 'labeled-response'], message.tags);
}
this.sendNAMES(targetChannel, batchLabel);
if (messageLabel) {
this.sendMessage(this.server.name, 'BATCH', [`-${batchLabel}`]);
}
}
doPART(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
const reason = (message.params.length === 2) ? message.params[1] : "";
this.apiCall.post(`/rooms/${targetChannel.roomId}/leave`, {"reason": reason}).then(response => {
if (response.status === 200) {
@ -489,9 +517,9 @@ export abstract class Client {
doTAGMSG(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (message.tags.has("+draft/react") && message.tags.has("+draft/reply")) {
const content = {
"m.relates_to": {
@ -539,9 +567,9 @@ export abstract class Client {
doTOPIC(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
if (message.params.length === 1) {
this.sendTOPIC(targetChannel);
return;
@ -571,9 +599,15 @@ export abstract class Client {
doWHO(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetChannel = this.getChannel(message.params[0]);
const targetChannel = this.getChannel(message.params[0], message);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel)) return;
if (!this.checkIfInChannel(targetChannel, message)) return;
const newTags = new Map();
if (message.tags.get('label')) {
const batchLabel = Math.random().toString(36).substring(2,7);
this.sendMessage(this.server.name, 'BATCH', [`+${batchLabel}`, 'labeled-response'], message.tags);
newTags.set('batch', batchLabel);
}
for (const matrixUser of targetChannel.matrixUsers.values()) {
const opStatus = targetChannel.getNickPowerLevelMapping(matrixUser.nick);
const userParams = [
@ -586,9 +620,32 @@ export abstract class Client {
`H${opStatus}`,
`0 ${matrixUser.realname}`
]
this.sendMessage(this.server.name, '352', userParams);
this.sendMessage(this.server.name, '352', userParams, newTags);
}
this.sendMessage(this.server.name, '315', [this.user.nick, targetChannel.name, "End of /WHO"], newTags);
if (message.tags.get('label')) {
this.sendMessage(this.server.name, 'BATCH', [`-${newTags.get('batch')}`]);
}
}
doWHOIS(message: IRCMessage) {
if (!this.checkIfRegistered() || !this.checkMinParams(message, 1))
return;
const targetUser = this.getMatrixUserFromNick(message.params[0], message);
if (!targetUser) return;
const tNick = targetUser.nick;
const newTags = new Map();
if (message.tags.get('label')) {
const batchLabel = Math.random().toString(36).substring(2,7);
this.sendMessage(this.server.name, 'BATCH', [`+${batchLabel}`, 'labeled-response'], message.tags);
newTags.set('batch', batchLabel);
}
this.sendMessage(this.server.name, '311', [this.user.nick, tNick, targetUser.ident, targetUser.hostname, '*', targetUser.realname], newTags);
this.sendMessage(this.server.name, '330', [this.user.nick, tNick, targetUser.accountName, 'is logged in as'], newTags);
this.sendMessage(this.server.name, '318', [this.user.nick, tNick, "End of /WHOIS list"], newTags);
if (message.tags.get('label')) {
this.sendMessage(this.server.name, 'BATCH', [`-${newTags.get('batch')}`]);
}
this.sendMessage(this.server.name, '315', [this.user.nick, targetChannel.name, "End of /WHO"]);
}
doRegistration(message: IRCMessage) {
@ -624,6 +681,7 @@ export abstract class Client {
sendMessage(prefix: string, command: string, params: string[], tags: Map<string, string> = new Map()) {
const capTagMapping = new Map([
['account', 'account-tag'],
['batch', 'batch'],
['label', 'labeled-response'],
['msgid', 'message-tags'],
['reflectionircd.chat/delete-message', 'reflectionircd.chat/edit-message'],