From 5afb6c7c3a10a7b95982ea76daa3e7d2d39eceab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Fri, 14 Jun 2024 20:49:56 +0200 Subject: [PATCH 1/9] Match IRC conventions when bridging file uploads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usually, IRC users just upload files somewhere and send a bare link, perhaps with a bit of explanation. The bridge originally did something like this for files: * f_[mtrx] uploaded an image: (20KiB) < https://matrix.org/_matrix/media/v3/download/something/some-other-thing/image.png > and something like this for code blocks: * f_[mtrx] sent a code block: https://matrix.org/_matrix/media/v3/download/something/some-other-thing That is quite unusual on IRC. This commit changes it to: https://matrix.org/_matrix/media/v3/download/something/some-other-thing/image.png (20KiB) https://matrix.org/_matrix/media/v3/download/something/some-other-thing Which is more natural. Signed-off-by: Ferass El Hafidi --- src/bridge/MatrixHandler.ts | 6 +++--- src/models/IrcAction.ts | 8 ++++---- src/models/MatrixAction.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 4e4616732..e8102ad65 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -1175,8 +1175,8 @@ export class MatrixHandler { if (codeBlockMatch) { const type = codeBlockMatch[1] ? ` ${codeBlockMatch[1]}` : ''; event.content = { - msgtype: "m.emote", - body: `sent a${type} code block: ${httpUrl}` + ...event.content, + body: `${httpUrl}` }; } else { @@ -1207,7 +1207,7 @@ export class MatrixHandler { // Modify the event to become a truncated version of the original // the truncation limits the number of lines sent to lineLimit. - const msg = '\n...(truncated)'; + const msg = '\n(truncated)'; const sendingEvent: MatrixMessageEvent = { ...event, content: { diff --git a/src/models/IrcAction.ts b/src/models/IrcAction.ts index 3768eb5e8..d4e3749c9 100644 --- a/src/models/IrcAction.ts +++ b/src/models/IrcAction.ts @@ -54,18 +54,18 @@ export class IrcAction { return new IrcAction(matrixAction.type, matrixAction.text, matrixAction.ts); case "image": return new IrcAction( - "emote", "uploaded an image: " + matrixAction.text, matrixAction.ts + "message", "" + matrixAction.text, matrixAction.ts ); case "video": return new IrcAction( - "emote", "uploaded a video: " + matrixAction.text, matrixAction.ts + "message", "" + matrixAction.text, matrixAction.ts ); case "audio": return new IrcAction( - "emote", "uploaded an audio file: " + matrixAction.text, matrixAction.ts + "message", "" + matrixAction.text, matrixAction.ts ); case "file": - return new IrcAction("emote", "posted a file: " + matrixAction.text, matrixAction.ts); + return new IrcAction("message", "" + matrixAction.text, matrixAction.ts); case "topic": if (matrixAction.text === null) { break; diff --git a/src/models/MatrixAction.ts b/src/models/MatrixAction.ts index 6f211b45d..50ec3ce60 100644 --- a/src/models/MatrixAction.ts +++ b/src/models/MatrixAction.ts @@ -220,12 +220,12 @@ export class MatrixAction { } if (filename) { - text = `${fileSize} < ${url} >`; + text = `${url} ${fileSize}`; } else { fileSize = fileSize ? ` ${fileSize}` : ""; // If not a filename, print the body - text = `${event.content.body}${fileSize} < ${url} >`; + text = `${url} ${event.content.body}${fileSize}`; } } } From a9660fba4a5e0f1a0c168d984b539ddd5037ef87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Fri, 14 Jun 2024 21:56:29 +0200 Subject: [PATCH 2/9] config.sample.yaml: change default reply-to format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It previously was: hi everyone, what's up? I'm about to migrate some stuff today ~ a while after ~ "hi everyone,..." <- hi, doing great! Now it is: hi everyone, what's up? I'm about to migrate some stuff today ~ a while after ~ f_: "hi everyone,..." <- hi, doing great! Some IRC clients may not create a ping when the nickname is surrounded with `<` and `>`. Signed-off-by: Ferass El Hafidi --- config.sample.yaml | 2 +- src/bridge/MatrixHandler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index ea2fbb686..3c8b85011 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -594,7 +594,7 @@ ircService: # format of replies sent shortly after the original message shortReplyTemplate: "$NICK: $REPLY" # format of replies sent a while after the original message - longReplyTemplate: "<$NICK> \"$ORIGINAL\" <- $REPLY" + longReplyTemplate: "$NICK: \"$ORIGINAL\" <- $REPLY" # how much time needs to pass between the reply and the original message to switch to the long format shortReplyTresholdSeconds: 300 # Ignore users mentioned in a io.element.functional_members state event when checking admin room membership diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index e8102ad65..83724a3b7 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -67,7 +67,7 @@ export const DEFAULTS: MatrixHandlerConfig = { replySourceMaxLength: 32, shortReplyTresholdSeconds: 5 * 60, shortReplyTemplate: "$NICK: $REPLY", - longReplyTemplate: "<$NICK> \"$ORIGINAL\" <- $REPLY", + longReplyTemplate: "$NICK: \"$ORIGINAL\" <- $REPLY", truncatedMessageTemplate: "(full message at <$URL>)", ignoreFunctionalMembersInAdminRooms: false, }; From 51ecb5cced505ab2c21733c8a1936ca336c5f032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Sun, 16 Jun 2024 15:46:53 +0200 Subject: [PATCH 3/9] src/irc/BridgedClient.ts: replace "M" letter prefix with "`" when nick starts with an invalid character MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looks more natural to IRC users. IRC users usually add an underscore at the beginning or a "`". With that, "M24Hacker[m]" becomes "`24Hacker[m]" Signed-off-by: Ferass El Hafidi --- src/irc/BridgedClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irc/BridgedClient.ts b/src/irc/BridgedClient.ts index 932bceb47..8e085c43e 100644 --- a/src/irc/BridgedClient.ts +++ b/src/irc/BridgedClient.ts @@ -738,9 +738,9 @@ export class BridgedClient extends EventEmitter { `Nick '${nick}' must start with a letter or special character (dash is not a special character).` ); } - // Add arbitrary letter prefix. This is important for guest user + // Add arbitrary prefix. This is important for guest user // IDs which are all numbers. - n = "M" + n; + n = "`" + n; } if (state.status === BridgedClientStatus.CONNECTED) { From e50005f82526b1611e2f8f6c1c0a16270e874214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Mon, 17 Jun 2024 19:17:08 +0200 Subject: [PATCH 4/9] src/bridge/MatrixHandler.ts: add `s` modifier to REPLY_REGEX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/matrix-org/matrix-appservice-irc/issues/1521 Signed-off-by: Ferass El Hafidi --- src/bridge/MatrixHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 83724a3b7..55b6c9eda 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -1297,7 +1297,7 @@ export class MatrixHandler { const bridgeIntent = this.ircBridge.getAppServiceBridge().getIntent(); // strips out the quotation of the original message, if needed const replyText = (body: string): string => { - const REPLY_REGEX = /> <(.*?)>(.*?)\n\n([\s\S]*)/; + const REPLY_REGEX = /> <(.*?)>(.*?)\n\n([\s\S]*)/s; const match = REPLY_REGEX.exec(body); if (match === null || match.length !== 4) { return body; From 9c083d8d127a4364e34c19efec38605cc23c8147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Mon, 17 Jun 2024 21:33:14 +0200 Subject: [PATCH 5/9] src/bridge/MatrixHandler.ts: handle replying to self MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When someone sent a message: Hello? And then replied to their own message: > <@funderscore:nova.astraltech.org> Hello? Hi? What would be sent on IRC would either be this: Hello? f_[mtrx]: Hi? Or: Hello? -- a while later -- f_[mtrx]: "Hello?" <- Hi? Both of which are confusing because usually nobody pings themself on IRC. This commit treats replies to self differently, and introduces a new `selfReplyTemplate` config option, so that the reply gets bridged as: Hello? Hi? Which is a bit more natural. Signed-off-by: Ferass El Hafidi --- config.sample.yaml | 2 ++ config.schema.yml | 2 ++ src/bridge/MatrixHandler.ts | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index 3c8b85011..5996fdf74 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -595,6 +595,8 @@ ircService: shortReplyTemplate: "$NICK: $REPLY" # format of replies sent a while after the original message longReplyTemplate: "$NICK: \"$ORIGINAL\" <- $REPLY" + # format of replies where the sender of the original message is the same as the sender of the reply + selfReplyTemplate: "<$NICK> $ORIGINAL\n$REPLY" # how much time needs to pass between the reply and the original message to switch to the long format shortReplyTresholdSeconds: 300 # Ignore users mentioned in a io.element.functional_members state event when checking admin room membership diff --git a/config.schema.yml b/config.schema.yml index 74bd389a1..41f988eb8 100644 --- a/config.schema.yml +++ b/config.schema.yml @@ -169,6 +169,8 @@ properties: type: "string" shortReplyTresholdSeconds: type: "integer" + selfReplyTemplate: + type: "string" ignoreFunctionalMembersInAdminRooms: type: "boolean" mediaProxy: diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 55b6c9eda..e6246a169 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -55,6 +55,8 @@ export interface MatrixHandlerConfig { shortReplyTemplate: string; // Format of replies sent a while after the original message longReplyTemplate: string; + // format of replies where the sender of the original message is the same as the sender of the reply + selfReplyTemplate: string; // Format of the text explaining why a message is truncated and pastebinned truncatedMessageTemplate: string; // Ignore io.element.functional_members members joining admin rooms. @@ -68,6 +70,7 @@ export const DEFAULTS: MatrixHandlerConfig = { shortReplyTresholdSeconds: 5 * 60, shortReplyTemplate: "$NICK: $REPLY", longReplyTemplate: "$NICK: \"$ORIGINAL\" <- $REPLY", + selfReplyTemplate: "<$NICK> $ORIGINAL\n$REPLY", truncatedMessageTemplate: "(full message at <$URL>)", ignoreFunctionalMembersInAdminRooms: false, }; @@ -1392,7 +1395,11 @@ export class MatrixHandler { let replyTemplate: string; const thresholdMs = (this.config.shortReplyTresholdSeconds) * 1000; - if (rplSource && event.origin_server_ts - cachedEvent.timestamp > thresholdMs) { + if (cachedEvent.sender === event.sender) { + // They're replying to their own message. + replyTemplate = this.config.selfReplyTemplate; + } + else if (rplSource && event.origin_server_ts - cachedEvent.timestamp > thresholdMs) { replyTemplate = this.config.longReplyTemplate; } else { From e8e2429a1586ccfa1889135ff8b1f260dff32861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Thu, 12 Sep 2024 12:11:02 +0200 Subject: [PATCH 6/9] spec/: fix tests to match previous changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferass El Hafidi --- spec/integ/matrix-to-irc.spec.js | 23 +++++++++++------------ spec/unit/BridgedClient.spec.js | 4 ++-- src/bridge/MatrixHandler.ts | 1 - 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index 5393e0cb5..9e68227a5 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -298,7 +298,7 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - expect(text).toEqual(`<${repliesUser.nick}> "This is the real message" <- Reply Text`); + expect(text).toEqual(`${repliesUser.nick}: "This is the real message" <- Reply Text`); } ); const formatted_body = constructHTMLReply( @@ -389,7 +389,7 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - expect(text).toEqual(`<${repliesUser.nick}> "This..." <- Reply Text`); + expect(text).toEqual(`${repliesUser.nick}: "This..." <- Reply Text`); } ); const formatted_body = constructHTMLReply( @@ -499,7 +499,7 @@ describe("Matrix-to-IRC message bridging", function() { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - expect(text).toEqual(' "Message #2" <- Message #3'); + expect(text).toEqual('M-friend: "Message #2" <- Message #3'); } ); @@ -650,7 +650,7 @@ describe("Matrix-to-IRC message bridging", function() { }); }); - it("should bridge mutliline code blocks as IRC action with URL", function(done) { + it("should bridge mutliline code blocks as a URL", function(done) { let tBody = "```javascript\n" + " expect(text.indexOf(\"javascript\")).not.toEqual(-1);\n" + @@ -662,13 +662,12 @@ describe("Matrix-to-IRC message bridging", function() { const sdk = env.clientMock._client(config._botUserId); sdk.uploadContent.and.returnValue(Promise.resolve("mxc://deadbeefcafe")); - env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", (client, channel, text) => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", (client, channel, text) => { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); // don't be too brittle when checking this, but I expect to see the // code type and the media proxy url - expect(text.indexOf('javascript')).not.toEqual(-1); expect(text.includes(config.ircService.mediaProxy.publicUrl)).toEqual(true); done(); }); @@ -713,11 +712,11 @@ describe("Matrix-to-IRC message bridging", function() { }); }); - it("should bridge matrix images as IRC action with a URL", function(done) { + it("should bridge matrix images as a URL", function(done) { const tBody = "the_image.jpg"; const tMxcSegment = "/somecontentid"; - env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", (client, channel, text) => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", (client, channel, text) => { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); @@ -737,11 +736,11 @@ describe("Matrix-to-IRC message bridging", function() { }); }); - it("should bridge matrix files as IRC action with a URL", function(done) { + it("should bridge matrix files as a URL", function(done) { const tBody = "a_file.apk"; const tMxcSegment = "/somecontentid"; - env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", (client, channel, text) => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", (client, channel, text) => { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); @@ -1074,11 +1073,11 @@ describe("Matrix-to-IRC message bridging with media URL and drop time", function expect(said).toBe(true); }); - it("should bridge matrix files as IRC action with a configured media URL", function(done) { + it("should bridge matrix files as IRC message with a configured media URL", function(done) { let tBody = "a_file.apk"; let tMxcSegment = "/somecontentid"; - env.ircMock._whenClient(roomMapping.server, testUser.nick, "action", + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", function(client, channel, text) { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); diff --git a/spec/unit/BridgedClient.spec.js b/spec/unit/BridgedClient.spec.js index 0c1738ed5..6e7076200 100644 --- a/spec/unit/BridgedClient.spec.js +++ b/spec/unit/BridgedClient.spec.js @@ -31,8 +31,8 @@ describe("BridgedClient", function() { expect(BridgedClient.getValidNick("f+/\u3052oobar", false, STATE_DISC)).toBe("foobar"); }); it("will ensure nicks start with a letter or special character", function() { - expect(BridgedClient.getValidNick("-foobar", false, STATE_DISC)).toBe("M-foobar"); - expect(BridgedClient.getValidNick("12345", false, STATE_DISC)).toBe("M12345"); + expect(BridgedClient.getValidNick("-foobar", false, STATE_DISC)).toBe("`-foobar"); + expect(BridgedClient.getValidNick("12345", false, STATE_DISC)).toBe("`12345"); }); it("will throw if the nick is invalid", function() { expect(() => BridgedClient.getValidNick("f+/\u3052oobar", true, STATE_DISC)).toThrowError(); diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index e6246a169..b833d6a5a 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -1176,7 +1176,6 @@ export class MatrixHandler { // we check event.content.body since ircAction already has the markers stripped const codeBlockMatch = event.content.body.match(/^```(\w+)?/); if (codeBlockMatch) { - const type = codeBlockMatch[1] ? ` ${codeBlockMatch[1]}` : ''; event.content = { ...event.content, body: `${httpUrl}` From 690dcc32af6e9c6ccc35545c18c4085d8be668fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Tue, 6 Aug 2024 13:56:41 +0200 Subject: [PATCH 7/9] spec/integ/matrix-to-irc: add test for self-reply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferass El Hafidi --- spec/integ/matrix-to-irc.spec.js | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index 9e68227a5..688e1c416 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -234,6 +234,52 @@ describe("Matrix-to-IRC message bridging", function() { }); }); + it("should bridge matrix replies to self as self-replies", async () => { + // Trigger an original event + await env.mockAppService._trigger("type:m.room.message", { + content: { + body: "This is the real message", + msgtype: "m.text" + }, + room_id: roomMapping.roomId, + sender: repliesUser.id, + event_id: "$original:bar.com", + origin_server_ts: Date.now(), + type: "m.room.message" + }); + const p = env.ircMock._whenClient(roomMapping.server, repliesUser.nick, "say", + (client, channel, text) => { + expect(client.nick).toEqual(repliesUser.nick); + expect(client.addr).toEqual(roomMapping.server); + expect(channel).toEqual(roomMapping.channel); + expect(text).toEqual(`<${repliesUser.nick}> This is the real message\nReply Text`); + } + ); + const formatted_body = constructHTMLReply( + "This is the fake message", + "@somedude:bar.com", + "Reply text" + ); + await env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", + formatted_body, + format: "org.matrix.custom.html", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$original:bar.com" + } + }, + }, + sender: repliesUser.id, + room_id: roomMapping.roomId, + origin_server_ts: Date.now(), + type: "m.room.message" + }); + await p; + }); + it("should bridge rapid matrix replies as short replies", async () => { // Trigger an original event await env.mockAppService._trigger("type:m.room.message", { From 5cebd5e6eddf271d0947e5f3f5fe37caeae874a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20=E2=80=9Etadzik=E2=80=9D=20So=C5=9Bnierz?= Date: Thu, 12 Sep 2024 12:15:23 +0200 Subject: [PATCH 8/9] changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferass El Hafidi --- changelog.d/1822.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1822.bugfix diff --git a/changelog.d/1822.bugfix b/changelog.d/1822.bugfix new file mode 100644 index 000000000..2fb1694b3 --- /dev/null +++ b/changelog.d/1822.bugfix @@ -0,0 +1 @@ +Change format for file uploads, codeblocks, long replies, self-replies and nicknames. Fixes #1521. From 4bb409e38f69b159400347cb70586d0caa1efb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferass=20El=C2=A0Hafidi?= Date: Fri, 14 Jun 2024 21:54:53 +0200 Subject: [PATCH 9/9] config.sample.yaml: update outdated comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ferass El Hafidi --- config.sample.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index 5996fdf74..a3e967003 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -332,7 +332,7 @@ ircService: ircClients: # The template to apply to every IRC client nick. This MUST have either # $DISPLAY or $USERID or $LOCALPART somewhere in it. - # Optional. Default: "M-$DISPLAY". Example: "M-Alice". + # Optional. Default: "$DISPLAY[m]". Example: "Alice[m]". nickTemplate: "$DISPLAY[m]" # True to allow virtual IRC clients to change their nick on this server # by issuing !nick commands to the IRC AS bot.