add draft/multiline

This commit is contained in:
emerson 2022-10-18 18:24:02 -04:00
parent 22249f8dff
commit dd054af414
6 changed files with 111 additions and 22 deletions

View file

@ -5,6 +5,7 @@
- `AWAY` and `away-notify`
- Typing notifications
- Sticker support (M->I only)
- IRC Multiline message support
## Fixed
- labeled-response is sent correctly for all commands

View file

@ -1,10 +1,6 @@
# ReflectionIRCd
ReflectionIRCd is an IRCd that bridges Matrix, allowing you to use an IRC client to chat on Matrix.
This is still in beta. There's `console.log`s all over the place, an inadequate amount of testing and error/bounds checking, several bugs, etc.
That said, it is usable for basic chatting; if you use a good IRC client, you won't have many problems. See the [support matrix](#feature-support) (pun intended) for what it can do. The issue tracker is here: https://todo.sr.ht/~emerson/reflectionircd
ReflectionIRCd is an IRCd that bridges Matrix, allowing you to use an IRC client to chat on Matrix. See the [support matrix](#feature-support) (pun intended) for what it can do. The issue tracker is here: https://todo.sr.ht/~emerson/reflectionircd
## Highlights
@ -35,6 +31,7 @@ Then `npm ci`, `npm run build`, and `node reflection.js config.json` (replace `c
* You can't open a PM to individual users, since a "PM" in Matrix is just another room, and you can't join rooms from IRC yet.
* There's no IRC equivalent to Spaces. Since they are just regular rooms, they show up in IRC as just regular rooms, however, you can't send messages to them.
* In order to highlight users, you need to prefix their nick with an `@`, so `@emerson: hi`. Reflection automatically expands this to the user's MXID, so even though it's annoying, it's needed to stop inadvertent highlights for now.
* In general, Reflection trusts IRC clients to do the right thing, and doesn't have a lot of error-checking of IRC messages (it does have robust error checking for the Matrix side because Matrix sends a lot of error-filled messages).
## FAQs
@ -54,10 +51,10 @@ Because matrix room names are often long and have characters that IRC channel na
If you have `server-time` enabled on your client, you might find messages arriving late or out of order. Similarly, if you're joined to the same channel from different homeservers, you may see messages appear on one several minutes/hours before the other(s). This is called "federation", and is considered a feature of Matrix.
### Does IRC actually support all of these cool new features?
In theory, yes. In reality, no. There's currently no free, standalone IRC client that supports reactions, replies, editing/deleting messages, and channel renaming. But one day they might, and when they do, Reflection will be ready to help them in their quest for world domination.
In theory, yes. In reality, no. There's currently no free, standalone IRC client that supports reactions, replies, editing/deleting messages, and channel renaming. But one day they might, and when they do, Reflection will be ready to support them.
### What does "Sync is lagging" mean?
The matrix sync is set with a timeout of 15 seconds. The server is supposed to return an HTTP response after 15 seconds (if not sooner). If the server doesn't respond after 20 seconds, that message is printed to the console to warn you that the homeserver is lagging.
The matrix sync is set with a timeout of 15 seconds (configurable in milliseconds as the `syncTimeout` value in the config file). The server is supposed to return an HTTP response after the timeout (if not sooner). If the server doesn't respond after 20 seconds (also configurable as `lagWarningLimit`), that message is printed to the console to warn you that the homeserver is lagging.
### Why is there a random JSON object in my console?
That means you were sent an event type that I didn't know existed. Please hop into [the chat](#support) and tell me what the `type` value is.
@ -72,7 +69,7 @@ On Libera I'm `emerson`, on Matrix I'm `@emersonveenstra:matrix.org`, feel free
✅ - Implemented
🟨 - Partially implemented, see notes
❌ - Not implemented yet
❌ - Not implemented
<small>(IRCv3)</small> denotes IRC features that might not be available in all clients

View file

@ -22,6 +22,7 @@ export abstract class Client {
["account-tag", ""],
["batch", ""],
["draft/channel-rename", ""],
["draft/multiline", "max-bytes=4096,max-lines=20"],
["echo-message", ""],
["reflectionircd.chat/edit-message", ""],
["reflectionircd.chat/extended-invite", ""],
@ -406,7 +407,66 @@ export abstract class Client {
}
doMultiline(batch: Batch) {
if (batch.messages.size === 0) {
return;
}
let fullMessage = '';
const firstMessage = [...batch.messages][0];
const msgType = (firstMessage.command === 'NOTICE') ? 'm.notice' : 'm.text';
const targetChannel = this.getChannel(firstMessage.params[0], firstMessage);
if (!this.user || !targetChannel) return;
if (!this.checkIfInChannel(targetChannel, firstMessage)) return;
if (targetChannel.roomType === "m.space") {
this.sendMessage(this.server.name, "NOTICE", [targetChannel.name, "Sending messages to spaces is not allowed"], batch.openingBatch.tags);
return;
}
for (const msg of batch.messages) {
console.log(firstMessage.params[1], msg.params[1]);
const separator = (msg.tags.has('draft/multiline-concat') || firstMessage.params[1] === msg.params[1]) ? '' : '\n';
fullMessage = `${fullMessage}${separator}${msg.params[1]}`;
console.log(fullMessage);
}
const highlightFilteredMsg = fullMessage.split(" ").map(w => {
if (!w.startsWith('@')) return w;
const endingCharMatch = w.match(/[,:]$/);
const endingChar = (endingCharMatch) ? endingCharMatch[0] : "";
const endingCharIndex = (endingCharMatch) ? endingCharMatch.index : 0;
const nickToSearch = (endingCharIndex === 0) ? w.substring(1) : w.substring(1, endingCharIndex);
const maybeHighlight = targetChannel.matrixUsers.get(nickToSearch);
return (maybeHighlight) ? `${maybeHighlight.mxid}${endingChar}` : w;
})
const content = {
"body": highlightFilteredMsg.join(" "),
"msgtype": msgType,
"m.relates_to": {}
}
if (batch.openingBatch.tags.has("+draft/reply")) {
content["m.relates_to"] = {
"m.in_reply_to": {
"event_id": batch.openingBatch.tags.get("+draft/reply")
}
}
}
const newTxnid = randomUUID();
this.apiCall.put(`/rooms/${targetChannel.roomId}/send/m.room.message/${newTxnid}`, content).then(r => {
const maybeEventID = r.data["event_id"];
if (maybeEventID) {
this.server.eventIdStore.set(maybeEventID, this);
const maybeLabel = batch.openingBatch.tags.get("label") || "";
if (maybeLabel !== "") {
this.server.eventIDToLabel.set(maybeEventID, maybeLabel)
}
}
}).catch(function (error) {
if (error.response) {
console.log(error.response.data);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
console.log(error.config);
}
});
}
doMSG(message: IRCMessage) {

View file

@ -66,6 +66,7 @@ function encodeTag(value: string): string {
function addToTags(key: string): boolean {
const tagsToPass = [
'batch',
'draft/multiline-concat',
'reflectionircd.chat/delete-message',
'reflectionircd.chat/edit-message',
'label',

View file

@ -489,19 +489,49 @@ export class Server {
messageContent = `\x01ACTION shared ${messageContent}: ${uri}\x01`;
}
const msgArray = (messageContent.indexOf('\n') !== -1) ? messageContent.split('\n'): [messageContent];
msgArray.forEach((msg: string) => {
if (msg) {
this.clients.forEach((client) => {
if (this.eventIdStore.get(event["event_id"]) === client) {
if (client.enabledCaps.has('echo-message')) {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
} else {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
});
if (msgArray.length === 1) {
const msg = msgArray[0];
if (!msg) {
return;
}
});
this.clients.forEach((client) => {
if (this.eventIdStore.get(event["event_id"]) === client) {
if (client.enabledCaps.has('echo-message')) {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
} else {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
}
});
} else {
this.clients.forEach(client => {
if (this.eventIdStore.get(event["event_id"]) === client && !client.enabledCaps.has('echo-message')) {
return;
}
if (client.enabledCaps.has('draft/multiline')) {
const batchLabel = Math.random().toString(36).substring(2,7);
const batchTags = new Map();
batchTags.set('batch', batchLabel);
client.sendMessage(sourceUser.getMask(), 'BATCH', [`+${batchLabel}`, 'draft/multiline', targetChannel.name], tags);
for (const msg of msgArray) {
if (msg) {
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], batchTags)
});
}
}
client.sendMessage(sourceUser.getMask(), 'BATCH', [`-${batchLabel}`], new Map());
} else {
for (const msg of msgArray) {
if (msg) {
this.clients.forEach((client) => {
client.sendMessage(sourceUser.getMask(), ircCommand, [targetChannel.name, msg], tags)
});
}
}
}
})
}
}
convertPLToMode(pl: number, direction: string) {

View file

@ -9,7 +9,7 @@ const client = connect({
rejectUnauthorized: false
}, () => {
client.write("CAP LS 302\r\n");
client.write("CAP REQ :account-tag batch draft/channel-rename echo-message reflectionircd.chat/edit-message reflectionircd.chat/extended-invite extended-join invite-notify labeled-response message-tags sasl server-time\r\n");
client.write("CAP REQ :account-tag batch draft/channel-rename draft/multiline echo-message reflectionircd.chat/edit-message reflectionircd.chat/extended-invite extended-join invite-notify labeled-response message-tags sasl server-time\r\n");
const saslPassword = Buffer.from(`\0user\0${config.SASLPassword}`).toString('base64');
client.write(`AUTHENTICATE ${saslPassword}\r\n`)
client.write("CAP END\r\n")