mirror of
https://git.sr.ht/~emerson/reflectionircd
synced 2025-04-13 09:59:52 +00:00
implement labeled-response
This commit is contained in:
parent
b1677bb6ad
commit
c8a0a7dd6a
1 changed files with 101 additions and 43 deletions
144
src/Client.ts
144
src/Client.ts
|
@ -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'],
|
||||
|
|
Loading…
Add table
Reference in a new issue