mirror of
https://git.sr.ht/~emerson/reflectionircd
synced 2025-08-05 16:59:10 +00:00
lots of changes
This commit is contained in:
parent
4b8ee81b23
commit
209fe0451a
7 changed files with 281 additions and 29 deletions
32
README.md
32
README.md
|
@ -14,32 +14,38 @@ That said, it is usable for basic chatting.
|
||||||
|
|
||||||
## Feature support
|
## Feature support
|
||||||
|
|
||||||
✅ - Fully supported
|
✅ - Implemented
|
||||||
🟨 - Partially supported, see notes
|
🟨 - Partially implemented, see notes
|
||||||
❌ - Not implemented yet
|
❌ - Not implemented yet
|
||||||
|
⬜ - Not applicable
|
||||||
|
|
||||||
<small>(IRCv3)</small> denotes IRC features that might not be available in all clients
|
<small>(IRCv3)</small> denotes IRC features that might not be available in all clients
|
||||||
|
|
||||||
| Name | M->I | I->M | Notes |
|
| Name | M->I | I->M | Notes |
|
||||||
| ---- | :--: | :--: | ----- |
|
| ---- | :--: | :--: | ----- |
|
||||||
| text, notice, emote messages | ✅ | ✅ ||
|
| text, notice, emote messages | ✅ | ✅ ||
|
||||||
| image, file, audio, video messages | 🟨 | ❌ | Show up as links on IRC |
|
| image, file, audio, video messages | 🟨 | ⬜ | Show up as links on IRC |
|
||||||
| Channel joins | ✅ | ❌ ||
|
| Channel joins | ✅ | ❌ ||
|
||||||
| Channel parts | ✅ | ❌ ||
|
| Channel parts | ✅ | ✅ ||
|
||||||
| Channel kicks | ✅ | ❌ ||
|
| Channel kicks | ✅ | ✅ ||
|
||||||
| Channel bans | 🟨 | ❌ | Bans show up on IRC as kicks |
|
| Channel bans | 🟨 | ❌ | Single-user bans show up on IRC as kicks, there's no banlist yet |
|
||||||
| Channel invites | ✅ | ❌ ||
|
| Channel invites | ✅ | ✅ ||
|
||||||
| Channel topics | ✅ | ❌ ||
|
| Channel topics | ✅ | ✅ ||
|
||||||
| Channel powers | ❌ | ❌ ||
|
| Channel powers | ❌ | ❌ ||
|
||||||
| Encrypted rooms | ❌ | ❌ ||
|
| Channel lists/searching | ⬜ | ❌ ||
|
||||||
|
| Encrypted rooms | ❌ | ⬜ ||
|
||||||
|
| Rich text | ❌ | ❌ ||
|
||||||
|
| Presence | ❌ | ❌ ||
|
||||||
| Channel renaming <small>(IRCv3)</small> | 🟨 | ❌ | Only when the canonical alias changes |
|
| Channel renaming <small>(IRCv3)</small> | 🟨 | ❌ | Only when the canonical alias changes |
|
||||||
| Message replies <small>(IRCv3)</small> | ✅ | ❌ ||
|
| Message replies <small>(IRCv3)</small> | ✅ | ✅ | Clients without support will still receive the reply message, but it won't be marked as a reply |
|
||||||
| Message reactions <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Message reactions <small>(IRCv3)</small> | 🟨 | 🟨 | Can't undo reactions yet |
|
||||||
| Chat history <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Extended invites <small>(IRCv3)</small> | ✅ | ✅ ||
|
||||||
|
| Chat history <small>(IRCv3)</small> | ⬜ | ❌ ||
|
||||||
| Multiline messages <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Multiline messages <small>(IRCv3)</small> | ❌ | ❌ ||
|
||||||
| Global display names <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Global display names <small>(IRCv3)</small> | ❌ | ❌ ||
|
||||||
| Per-room display names <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Per-room display names <small>(IRCv3)</small> | ❌ | ❌ ||
|
||||||
| Message editing/deletion <small>(IRCv3)</small> | ❌ | ❌ ||
|
| Message editing <small>(IRCv3)</small> | ❌ | ❌ ||
|
||||||
|
| Message deletion <small>(IRCv3)</small> | ❌ | ❌ ||
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
Copy `config.example.json` to `config.json`, edit the values if needed, then `npm run build` and then `node reflection.js` to start it
|
Copy `config.example.json` to `config.json`, edit the values if needed, then `npm run build` and then `node reflection.js` to start it
|
||||||
|
|
46
docs/specs/delete-message.md
Normal file
46
docs/specs/delete-message.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: Message Deletion
|
||||||
|
layout: spec
|
||||||
|
work-in-progress: true
|
||||||
|
copyrights:
|
||||||
|
-
|
||||||
|
name: "Emerson Veenstra"
|
||||||
|
email: "ircv3@emersonveenstra.net"
|
||||||
|
period: "2022"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes for implementing work-in-progress version
|
||||||
|
|
||||||
|
This is a work-in-progress specification.
|
||||||
|
|
||||||
|
Software implementing this work-in-progress specification MUST NOT use the
|
||||||
|
unprefixed `delete-message` capability name. Instead, implementations SHOULD
|
||||||
|
use the `draft/delete-message` capability name to be interoperable with other
|
||||||
|
software implementing a compatible work-in-progress version.
|
||||||
|
|
||||||
|
The final version of the specification will use an unprefixed capability name.
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This specification describes a standardized way to signal to clients that a previously
|
||||||
|
sent message should no longer be displayed.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Servers and clients implementing this spec MUST also implement the `message-tags` capability.
|
||||||
|
|
||||||
|
To request message deletion, clients send a `DELETEMSG` to the channel or user of the original message.
|
||||||
|
This `DELETEMSG` MUST have the tag `draft/delete-message` with a required value of the `msgid` of the
|
||||||
|
message to delete. It MAY have an optional second parameter to specify a reason for deletion.
|
||||||
|
|
||||||
|
Clients who receive a `DELETEMSG` with the `draft/delete-message` tag MUST remove all displayed content from
|
||||||
|
the specified `msgid`. If the original message is ephemeral, clients SHOULD remove it entirely;
|
||||||
|
otherwise they SHOULD replace it with the deletion reason, or a generic substitute.
|
||||||
|
|
||||||
|
Servers MUST ensure that the user requesting deletion has sufficient privileges to delete the specified
|
||||||
|
message. Servers MUST remove the content of the original message from all persistent history stores, and
|
||||||
|
MAY replace the content with a generic deletion message if needed.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
51
docs/specs/extended-invite.md
Normal file
51
docs/specs/extended-invite.md
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
title: Extended Invites
|
||||||
|
layout: spec
|
||||||
|
work-in-progress: true
|
||||||
|
copyrights:
|
||||||
|
-
|
||||||
|
name: "Emerson Veenstra"
|
||||||
|
email: "ircv3@emersonveenstra.net"
|
||||||
|
period: "2022"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes for implementing work-in-progress version
|
||||||
|
|
||||||
|
This is a work-in-progress specification.
|
||||||
|
|
||||||
|
Software implementing this work-in-progress specification MUST NOT use the
|
||||||
|
unprefixed `extended-invite` capability name. Instead, implementations SHOULD
|
||||||
|
use the `draft/extended-invite` capability name to be interoperable with other
|
||||||
|
software implementing a compatible work-in-progress version.
|
||||||
|
|
||||||
|
The final version of the specification will use an unprefixed capability name.
|
||||||
|
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This specification extends the `INVITE` command to allow for an optional reason.
|
||||||
|
The reason can be used to give more context around why the invite was sent.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Servers implementing this spec MUST also implement the `invite-notify` capability. Clients SHOULD
|
||||||
|
negotiate the `invite-notify` capability when negotiating `extended-invite`.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
Clients that have negotiated the `extended-invite` capability MAY add a final parameter on
|
||||||
|
an `/INVITE` command. This parameter is to give the target user context for the invite. This
|
||||||
|
parameter MUST NOT cause the message to exceed the maximum allowable line length of the
|
||||||
|
server.
|
||||||
|
|
||||||
|
Servers that implement `extended-invite` MUST accept `INVITE` commands with three parameters.
|
||||||
|
If the user receiving the invite has negotiated `extended-invite`, the server sends the
|
||||||
|
final parameter to that user. Additionally, the final parameter is added to the `INVITE` command
|
||||||
|
sent to any users that have negotiated both the `extended-invite` and `invite-notify` capabilities.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
C: INVITE emerson #project-test :We're testing out our project in here!
|
||||||
|
S: n!u@h INVITE emerson #project-test :We're testing out our project in here!
|
|
@ -145,6 +145,9 @@ export class Channel {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (event["type"]) {
|
switch (event["type"]) {
|
||||||
|
case 'm.reaction':
|
||||||
|
this.handleMatrixReaction(event);
|
||||||
|
break;
|
||||||
case 'm.room.canonical_alias':
|
case 'm.room.canonical_alias':
|
||||||
this.updateRoomName(event);
|
this.updateRoomName(event);
|
||||||
break;
|
break;
|
||||||
|
@ -192,7 +195,7 @@ export class Channel {
|
||||||
case 'org.matrix.room.preview_urls':
|
case 'org.matrix.room.preview_urls':
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(event["type"]);
|
console.log(event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,8 +225,17 @@ export class Channel {
|
||||||
messageTags.set('time', new Date(event["origin_server_ts"]).toISOString());
|
messageTags.set('time', new Date(event["origin_server_ts"]).toISOString());
|
||||||
messageTags.set('account', sourceUser.accountName);
|
messageTags.set('account', sourceUser.accountName);
|
||||||
if (membershipStatus === "invite") {
|
if (membershipStatus === "invite") {
|
||||||
|
const reason = content["reason"];
|
||||||
this.ircUsers.forEach((user) => {
|
this.ircUsers.forEach((user) => {
|
||||||
user.sendToAllWithCap('invite-notify', sourceUser.getMask(), 'INVITE', [targetUser.nick, this.name], messageTags);
|
user.getClients().forEach(c => {
|
||||||
|
if (c.enabledCaps.has('invite-notify')) {
|
||||||
|
if (c.enabledCaps.has('draft/extended-invite')) {
|
||||||
|
c.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, this.name, reason], messageTags)
|
||||||
|
} else {
|
||||||
|
c.sendMessage(sourceUser.getMask(), 'INVITE', [targetUser.nick, this.name], messageTags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (membershipStatus === "join") {
|
else if (membershipStatus === "join") {
|
||||||
|
@ -311,6 +323,28 @@ export class Channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMatrixReaction(event: any) {
|
||||||
|
const thisMatrixUser = this.server.getOrCreateMatrixUser(event["sender"]);
|
||||||
|
if (!this.matrixUsers.has(thisMatrixUser.nick)) {
|
||||||
|
this.joinMatrixUser(thisMatrixUser, event);
|
||||||
|
}
|
||||||
|
const tags: Map<string, string> = new Map();
|
||||||
|
tags.set('msgid', event["event_id"]);
|
||||||
|
tags.set('account', thisMatrixUser.accountName);
|
||||||
|
tags.set('time', new Date(event["origin_server_ts"]).toISOString())
|
||||||
|
const reactionData = event["content"]?.['m.relates_to'];
|
||||||
|
if (!reactionData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const targetMsgid = reactionData["event_id"];
|
||||||
|
const reaction = reactionData["key"];
|
||||||
|
tags.set('+draft/reply', targetMsgid);
|
||||||
|
tags.set('+draft/react', reaction);
|
||||||
|
this.ircUsers.forEach((user) => {
|
||||||
|
user.sendToAllWithCap("message-tags", thisMatrixUser.getMask(), "TAGMSG", [this.name], tags)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleMatrixTopic(event: any) {
|
handleMatrixTopic(event: any) {
|
||||||
const topicText = event["content"]?.["topic"];
|
const topicText = event["content"]?.["topic"];
|
||||||
if (!topicText)
|
if (!topicText)
|
||||||
|
|
|
@ -60,6 +60,25 @@ export class Client {
|
||||||
this.doCAP(message);
|
this.doCAP(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'INVITE': {
|
||||||
|
const targetUser = this.server.getOrCreateMatrixUser(message.params[0])
|
||||||
|
const targetChannel = this.server.ircChannels.get(message.params[1]);
|
||||||
|
const reason = (message.params.length === 3) ? message.params[2] : "";
|
||||||
|
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
this.user.inviteMatrixUser(this, targetChannel, targetUser, reason, message.tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'KICK': {
|
||||||
|
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
||||||
|
const targetMxid = this.server.nickToMxid.get(message.params[1]);
|
||||||
|
if (!targetMxid)
|
||||||
|
return;
|
||||||
|
console.log(targetMxid);
|
||||||
|
const reason = (message.params.length === 3) ? message.params[2] : "";
|
||||||
|
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
this.user.kickMatrixUser(this, targetChannel, targetMxid, reason, message.tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'MODE': {
|
case 'MODE': {
|
||||||
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
||||||
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
@ -78,6 +97,13 @@ export class Client {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'PART': {
|
||||||
|
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
||||||
|
const reason = (message.params.length === 2) ? message.params[1] : "";
|
||||||
|
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
this.user.partMatrixRoom(this, targetChannel, reason, message.tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'PING': {
|
case 'PING': {
|
||||||
this.sendMessage(this.server.name, "PONG", message.params, message.tags);
|
this.sendMessage(this.server.name, "PONG", message.params, message.tags);
|
||||||
break;
|
break;
|
||||||
|
@ -88,6 +114,24 @@ export class Client {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'TAGMSG': {
|
||||||
|
if (this.user) {
|
||||||
|
this.user.sendTagToMatrix(message, this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'TOPIC': {
|
||||||
|
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
||||||
|
if (!this.user || !targetChannel || !targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
break;
|
||||||
|
if (message.params.length === 1) {
|
||||||
|
targetChannel.sendTopic(this, message.tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const topic = message.params[1];
|
||||||
|
this.user.changeRoomTopic(this, targetChannel, topic, message.tags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'WHO': {
|
case 'WHO': {
|
||||||
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
const targetChannel = this.server.ircChannels.get(message.params[0]);
|
||||||
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
if (this.user && targetChannel && targetChannel.ircUsers.get(this.user.nick))
|
||||||
|
|
|
@ -2,6 +2,7 @@ import axios from "axios";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { Channel } from "./Channel.js";
|
import { Channel } from "./Channel.js";
|
||||||
import { Client } from "./Client.js";
|
import { Client } from "./Client.js";
|
||||||
|
import { MatrixUser } from "./MatrixUser.js";
|
||||||
import { IRCMessage } from "./Message.js";
|
import { IRCMessage } from "./Message.js";
|
||||||
import { Server } from "./Server.js";
|
import { Server } from "./Server.js";
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ export class IRCUser {
|
||||||
this.txnIdStore = new Map();
|
this.txnIdStore = new Map();
|
||||||
this.nextBatch = "";
|
this.nextBatch = "";
|
||||||
this.initialSync = false;
|
this.initialSync = false;
|
||||||
this.syncIntervalID = setInterval(this.doSync.bind(this), 15000);
|
this.syncIntervalID = setInterval(this.doSync.bind(this), 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSynced() {
|
isSynced() {
|
||||||
|
@ -78,6 +79,49 @@ export class IRCUser {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inviteMatrixUser(client: Client, channel: Channel, target: MatrixUser, reason: string, passedTags: Map<string, string> = new Map()) {
|
||||||
|
const data = {
|
||||||
|
"reason": reason,
|
||||||
|
"user_id": target.mxid
|
||||||
|
}
|
||||||
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/invite?access_token=${this.accessToken}`, data).then(response => {
|
||||||
|
if (response.status !== 200)
|
||||||
|
client.sendMessage(this.server.name, "NOTICE", [this.nick, JSON.stringify(response.data)], passedTags);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
kickMatrixUser(client: Client, channel: Channel, target: string, reason: string, passedTags: Map<string, string> = new Map()) {
|
||||||
|
const data = {
|
||||||
|
"reason": reason,
|
||||||
|
"user_id": target
|
||||||
|
}
|
||||||
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/kick?access_token=${this.accessToken}`, data).then(response => {
|
||||||
|
if (response.status !== 200)
|
||||||
|
client.sendMessage(this.server.name, "NOTICE", [this.nick, JSON.stringify(response.data)], passedTags);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
partMatrixRoom(client: Client, channel: Channel, reason: string, passedTags: Map<string, string> = new Map()) {
|
||||||
|
axios.post(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/leave?access_token=${this.accessToken}`, {"reason": reason}).then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.clients.forEach(c => {
|
||||||
|
c.sendMessage(this.getMask(), "PART", [channel.name, reason], passedTags);
|
||||||
|
})
|
||||||
|
this.channels.delete(channel);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
client.sendMessage(this.server.name, "NOTICE", [this.nick, JSON.stringify(response.data)], passedTags);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
changeRoomTopic(client: Client, channel: Channel, topic: string, passedTags: Map<string, string> = new Map()) {
|
||||||
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/state/m.room.topic?access_token=${this.accessToken}`, {"topic": topic}).then(response => {
|
||||||
|
if (response.status !== 200)
|
||||||
|
client.sendMessage(this.server.name, "NOTICE", [this.nick, JSON.stringify(response.data)], passedTags);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
sendMessageToMatrix(message: IRCMessage, client: Client) {
|
sendMessageToMatrix(message: IRCMessage, client: Client) {
|
||||||
const channel = this.server.ircChannels.get(message.params[0]);
|
const channel = this.server.ircChannels.get(message.params[0]);
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
|
@ -95,12 +139,40 @@ export class IRCUser {
|
||||||
const content = {
|
const content = {
|
||||||
"body": msgbody,
|
"body": msgbody,
|
||||||
"msgtype": msgtype,
|
"msgtype": msgtype,
|
||||||
|
"m.relates_to": {}
|
||||||
|
}
|
||||||
|
if (message.tags.has("+draft/reply")) {
|
||||||
|
content["m.relates_to"] = {
|
||||||
|
"m.in_reply_to": {
|
||||||
|
"event_id": message.tags.get("+draft/reply")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const newTxnid = randomUUID();
|
const newTxnid = randomUUID();
|
||||||
this.txnIdStore.set(newTxnid, client);
|
this.txnIdStore.set(newTxnid, client);
|
||||||
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/send/m.room.message/${newTxnid}?access_token=${this.accessToken}`, content);
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/send/m.room.message/${newTxnid}?access_token=${this.accessToken}`, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendTagToMatrix(message: IRCMessage, client: Client) {
|
||||||
|
const channel = this.server.ircChannels.get(message.params[0]);
|
||||||
|
if (!channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(message.tags)
|
||||||
|
if (message.tags.has("+draft/react") && message.tags.has("+draft/reply")) {
|
||||||
|
const content = {
|
||||||
|
"m.relates_to": {
|
||||||
|
"event_id": message.tags.get("+draft/reply"),
|
||||||
|
"key": message.tags.get("+draft/react"),
|
||||||
|
"rel_type": "m.annotation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newTxnid = randomUUID();
|
||||||
|
this.txnIdStore.set(newTxnid, client);
|
||||||
|
axios.put(`https://${this.homeserver}/_matrix/client/v3/rooms/${channel.roomId}/send/m.reaction/${newTxnid}?access_token=${this.accessToken}`, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendToAll(prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
|
sendToAll(prefix: string, command: string, params: string[], tags: Map<string, string> = new Map(), skipClient: Client|null = null) {
|
||||||
this.clients.forEach(client => {
|
this.clients.forEach(client => {
|
||||||
if (client !== skipClient) {
|
if (client !== skipClient) {
|
||||||
|
|
|
@ -79,24 +79,23 @@ export function parseIRCMessage(rawLine: string) {
|
||||||
let command = '';
|
let command = '';
|
||||||
let params: string[] = [];
|
let params: string[] = [];
|
||||||
if (rawLine.startsWith('@')) {
|
if (rawLine.startsWith('@')) {
|
||||||
const tags = restOfMessage.substr(0, restOfMessage.indexOf(' '));
|
const tags = restOfMessage.substring(1, restOfMessage.indexOf(' '));
|
||||||
restOfMessage = restOfMessage.substr(restOfMessage.indexOf(' ')+1);
|
restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ')+1);
|
||||||
|
for (const tag of tags.split(';')) {
|
||||||
for (const tag in tags.split(';')) {
|
|
||||||
const valueSplit = tag.indexOf('=');
|
const valueSplit = tag.indexOf('=');
|
||||||
if (valueSplit === -1 && addToTags(tag)) {
|
if (valueSplit === -1 && addToTags(tag)) {
|
||||||
parsedTags.set(tag, '');
|
parsedTags.set(tag, '');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const key = tag.substr(0, valueSplit);
|
const key = tag.substring(0, valueSplit);
|
||||||
const value = tag.substr(valueSplit);
|
const value = tag.substring(valueSplit+1);
|
||||||
if (addToTags(key))
|
if (addToTags(key))
|
||||||
parsedTags.set(key, decodeTag(value));
|
parsedTags.set(key, decodeTag(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (restOfMessage.startsWith(':')) {
|
if (restOfMessage.startsWith(':')) {
|
||||||
prefix = restOfMessage.substr(0, restOfMessage.indexOf(' '));
|
prefix = restOfMessage.substring(0, restOfMessage.indexOf(' '));
|
||||||
restOfMessage = restOfMessage.substr(restOfMessage.indexOf(' ')+1);
|
restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ')+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restOfMessage.indexOf(' ') === -1) {
|
if (restOfMessage.indexOf(' ') === -1) {
|
||||||
|
@ -104,19 +103,19 @@ export function parseIRCMessage(rawLine: string) {
|
||||||
return new IRCMessage(parsedTags, prefix, command, params);
|
return new IRCMessage(parsedTags, prefix, command, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
command = restOfMessage.substr(0, restOfMessage.indexOf(' '));
|
command = restOfMessage.substring(0, restOfMessage.indexOf(' '));
|
||||||
restOfMessage = restOfMessage.substr(restOfMessage.indexOf(' ') + 1);
|
restOfMessage = restOfMessage.substring(restOfMessage.indexOf(' ') + 1);
|
||||||
|
|
||||||
let lastParam = '';
|
let lastParam = '';
|
||||||
if (restOfMessage.indexOf(' :') !== -1) {
|
if (restOfMessage.indexOf(' :') !== -1) {
|
||||||
lastParam = restOfMessage.substr(restOfMessage.indexOf(' :') + 2);
|
lastParam = restOfMessage.substring(restOfMessage.indexOf(' :') + 2);
|
||||||
restOfMessage = restOfMessage.substr(0, restOfMessage.indexOf(' :'));
|
restOfMessage = restOfMessage.substring(0, restOfMessage.indexOf(' :'));
|
||||||
}
|
}
|
||||||
|
|
||||||
params = restOfMessage.split(' ');
|
params = restOfMessage.split(' ');
|
||||||
if (lastParam !== '') {
|
if (lastParam !== '') {
|
||||||
params.push(lastParam);
|
params.push(lastParam);
|
||||||
}
|
}
|
||||||
//console.log(parsedTags, prefix, command, params);
|
console.log(parsedTags, prefix, command, params);
|
||||||
return new IRCMessage(parsedTags, prefix, command, params);
|
return new IRCMessage(parsedTags, prefix, command, params);
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue