forked from cadence/out-of-your-element
Compare commits
24 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 191a98e1dc | |||
| 678a1b77bb | |||
| 2aff1fbd06 | |||
| 92d6ada71b | |||
| d8fb4be509 | |||
| 4698835549 | |||
| e7cbfb9fc9 | |||
| 91bce76fc8 | |||
|
|
12f4103870 | ||
| e28eac6bfa | |||
| 857fb7583b | |||
| 59012d9613 | |||
| 953b3e7741 | |||
| 8c023cc936 | |||
| e9fe820666 | |||
| f742d8572a | |||
| 8224ed5341 | |||
| 0b513b7ee0 | |||
| 07ec9832b2 | |||
| a8b7d64e91 | |||
| 41692b11ff | |||
| d8c0a947f2 | |||
| 5c9e569a2a | |||
| 201814e9f4 |
17 changed files with 696 additions and 46 deletions
48
package-lock.json
generated
48
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "out-of-your-element",
|
"name": "out-of-your-element",
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "out-of-your-element",
|
"name": "out-of-your-element",
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chriscdn/promise-semaphore": "^3.0.1",
|
"@chriscdn/promise-semaphore": "^3.0.1",
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"entities": "^5.0.0",
|
"entities": "^5.0.0",
|
||||||
"get-relative-path": "^1.0.2",
|
"get-relative-path": "^1.0.2",
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.10",
|
||||||
"heatsync": "^2.7.2",
|
"heatsync": "^2.7.2",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
|
|
@ -276,9 +276,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
||||||
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
|
"integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1163,9 +1163,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1488,9 +1488,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/domino": {
|
"node_modules/domino": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/domino/-/domino-2.1.7.tgz",
|
||||||
"integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==",
|
"integrity": "sha512-3rcXhx0ixJV2nj8J0tljzejTF73A35LVVdnTQu79UAqTBFEgYPMgGtykMuu/BDqaOZphATku1ddRUn/RtqUHYQ==",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
|
|
@ -1587,9 +1587,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||||
"integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==",
|
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|
@ -1617,9 +1617,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fullstore": {
|
"node_modules/fullstore": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/fullstore/-/fullstore-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fullstore/-/fullstore-4.0.2.tgz",
|
||||||
"integrity": "sha512-Y9hN79Q1CFU8akjGnTZoBnTzlA/o8wmtBijJOI8dKCmdC7GLX7OekpLxmbaeRetTOi4OdFGjfsg4c5dxP3jgPw==",
|
"integrity": "sha512-syOev4kA0lZy4VkfBJZ99ZL4cIiSgiKt0G8SpP0kla1tpM1c+V/jBOVY/OqqGtR2XLVcM83SjFPFC3R2YIwqjQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1688,9 +1688,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/h3": {
|
"node_modules/h3": {
|
||||||
"version": "1.15.6",
|
"version": "1.15.10",
|
||||||
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.6.tgz",
|
"resolved": "https://registry.npmjs.org/h3/-/h3-1.15.10.tgz",
|
||||||
"integrity": "sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ==",
|
"integrity": "sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie-es": "^1.2.2",
|
"cookie-es": "^1.2.2",
|
||||||
|
|
@ -1937,9 +1937,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json-with-bigint": {
|
"node_modules/json-with-bigint": {
|
||||||
"version": "3.5.7",
|
"version": "3.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz",
|
||||||
"integrity": "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==",
|
"integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "out-of-your-element",
|
"name": "out-of-your-element",
|
||||||
"version": "3.4.0",
|
"version": "3.5.1",
|
||||||
"description": "A bridge between Matrix and Discord",
|
"description": "A bridge between Matrix and Discord",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"entities": "^5.0.0",
|
"entities": "^5.0.0",
|
||||||
"get-relative-path": "^1.0.2",
|
"get-relative-path": "^1.0.2",
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.10",
|
||||||
"heatsync": "^2.7.2",
|
"heatsync": "^2.7.2",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ const {prompt} = require("enquirer")
|
||||||
|
|
||||||
reg.ooye.web_password = passwordResponse.web_password
|
reg.ooye.web_password = passwordResponse.web_password
|
||||||
writeRegistration(reg)
|
writeRegistration(reg)
|
||||||
console.log("Saved. Restart Out Of Your Element to apply this change.")
|
console.log("Saved. This change should be applied instantly.")
|
||||||
})()
|
})()
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,16 @@ async function channelToKState(channel, guild, di) {
|
||||||
// Don't overwrite room topic if the topic has been customised
|
// Don't overwrite room topic if the topic has been customised
|
||||||
if (hasCustomTopic) delete channelKState["m.room.topic/"]
|
if (hasCustomTopic) delete channelKState["m.room.topic/"]
|
||||||
|
|
||||||
|
// Make voice channels be a Matrix voice room (MSC3417)
|
||||||
|
if (channel.type === DiscordTypes.ChannelType.GuildVoice) {
|
||||||
|
creationContent.type = "org.matrix.msc3417.call"
|
||||||
|
channelKState["org.matrix.msc3401.call/"] = {
|
||||||
|
"m.intent": "m.room",
|
||||||
|
"m.type": "m.voice",
|
||||||
|
"m.name": customName || channel.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't add a space parent if it's self service
|
// Don't add a space parent if it's self service
|
||||||
// (The person setting up self-service has already put it in their preferred space to be able to get this far.)
|
// (The person setting up self-service has already put it in their preferred space to be able to get this far.)
|
||||||
const autocreate = select("guild_active", "autocreate", {guild_id: guild.id}).pluck().get()
|
const autocreate = select("guild_active", "autocreate", {guild_id: guild.id}).pluck().get()
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,17 @@ test("channel2room: read-only discord channel", async t => {
|
||||||
t.equal(api.getCalled(), 2)
|
t.equal(api.getCalled(), 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("channel2room: voice channel", async t => {
|
||||||
|
const api = mockAPI(t)
|
||||||
|
const state = kstateStripConditionals(await channelToKState(testData.channel.voice, testData.guild.general, {api}).then(x => x.channelKState))
|
||||||
|
t.equal(state["m.room.create/"].type, "org.matrix.msc3417.call")
|
||||||
|
t.deepEqual(state["org.matrix.msc3401.call/"], {
|
||||||
|
"m.intent": "m.room",
|
||||||
|
"m.name": "🍞丨[8user] Piece",
|
||||||
|
"m.type": "m.voice"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("convertNameAndTopic: custom name and topic", t => {
|
test("convertNameAndTopic: custom name and topic", t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),
|
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ async function emojisToState(emojis, guild) {
|
||||||
if (e.data?.errcode === "M_TOO_LARGE") { // Very unlikely to happen. Only possible for 3x-series emojis uploaded shortly after animated emojis were introduced, when there was no 256 KB size limit.
|
if (e.data?.errcode === "M_TOO_LARGE") { // Very unlikely to happen. Only possible for 3x-series emojis uploaded shortly after animated emojis were introduced, when there was no 256 KB size limit.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error(`Trying to handle emoji ${emoji.name} (${emoji.id}), but...`)
|
e["emoji"] = {
|
||||||
|
name: emoji.name,
|
||||||
|
id: emoji.id
|
||||||
|
}
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,17 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.type === DiscordTypes.MessageType.ChannelFollowAdd) {
|
||||||
|
return [{
|
||||||
|
$type: "m.room.message",
|
||||||
|
msgtype: "m.emote",
|
||||||
|
body: `set this room to receive announcements from ${message.content}`,
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: tag`set this room to receive announcements from <strong>${message.content}</strong>`,
|
||||||
|
"m.mentions": {}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
let isInteraction = (message.type === DiscordTypes.MessageType.ChatInputCommand || message.type === DiscordTypes.MessageType.ContextMenuCommand) && message.interaction && "name" in message.interaction
|
let isInteraction = (message.type === DiscordTypes.MessageType.ChatInputCommand || message.type === DiscordTypes.MessageType.ContextMenuCommand) && message.interaction && "name" in message.interaction
|
||||||
let isThinkingInteraction = isInteraction && !!((message.flags || 0) & DiscordTypes.MessageFlags.Loading)
|
let isThinkingInteraction = isInteraction && !!((message.flags || 0) & DiscordTypes.MessageFlags.Loading)
|
||||||
|
|
||||||
|
|
@ -658,7 +669,7 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
const match = repliedToEventSenderMxid.match(/^@([^:]*)/)
|
const match = repliedToEventSenderMxid.match(/^@([^:]*)/)
|
||||||
assert(match)
|
assert(match)
|
||||||
repliedToDisplayName = referenced.author.username || match[1] || "a Matrix user" // grab the localpart as the display name, whatever
|
repliedToDisplayName = referenced.author.username || match[1] || "a Matrix user" // grab the localpart as the display name, whatever
|
||||||
repliedToUserHtml = `<a href="https://matrix.to/#/${repliedToEventSenderMxid}">${repliedToDisplayName}</a>`
|
repliedToUserHtml = tag`<a href="https://matrix.to/#/${repliedToEventSenderMxid}">${repliedToDisplayName}</a>`
|
||||||
} else {
|
} else {
|
||||||
repliedToDisplayName = referenced.author.global_name || referenced.author.username || "a Discord user"
|
repliedToDisplayName = referenced.author.global_name || referenced.author.username || "a Discord user"
|
||||||
repliedToUserHtml = repliedToDisplayName
|
repliedToUserHtml = repliedToDisplayName
|
||||||
|
|
@ -683,6 +694,12 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
+ html
|
+ html
|
||||||
body = `${repliedToDisplayName}: ${repliedToBody}`.split("\n").map(line => "> " + line).join("\n") // scenario 1 part B for mentions
|
body = `${repliedToDisplayName}: ${repliedToBody}`.split("\n").map(line => "> " + line).join("\n") // scenario 1 part B for mentions
|
||||||
+ "\n\n" + body
|
+ "\n\n" + body
|
||||||
|
} else if (referenced.type === DiscordTypes.MessageType.UserJoin) {
|
||||||
|
// Discord user join messages are bridged as joins, not text events. Generate substitute text for reply.
|
||||||
|
const joinerMxid = select("sim", "mxid", {user_id: referenced.author.id}).pluck().get()
|
||||||
|
const joinerHtml = joinerMxid ? tag`<a href="https://matrix.to/#/${joinerMxid}">${repliedToDisplayName}</a>` : tag`<strong>${repliedToDisplayName}</strong>`
|
||||||
|
html = `<blockquote>${joinerHtml} joined the room</blockquote>` + html
|
||||||
|
body = `> ${repliedToDisplayName} joined the room\n\n` + body
|
||||||
} else { // repliedToUnknownEvent
|
} else { // repliedToUnknownEvent
|
||||||
const dateDisplay = dUtils.howOldUnbridgedMessage(referenced.timestamp, message.timestamp)
|
const dateDisplay = dUtils.howOldUnbridgedMessage(referenced.timestamp, message.timestamp)
|
||||||
html = `<blockquote>In reply to ${dateDisplay} from ${repliedToDisplayName}:`
|
html = `<blockquote>In reply to ${dateDisplay} from ${repliedToDisplayName}:`
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const {MatrixServerError} = require("../../matrix/mreq")
|
||||||
const data = require("../../../test/data")
|
const data = require("../../../test/data")
|
||||||
const {mockGetEffectivePower} = require("../../matrix/utils.test")
|
const {mockGetEffectivePower} = require("../../matrix/utils.test")
|
||||||
const Ty = require("../../types")
|
const Ty = require("../../types")
|
||||||
|
const {db} = require("../../passthrough")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
|
|
@ -733,6 +734,31 @@ test("message2event: reply to a Discord message that wasn't bridged", async t =>
|
||||||
}])
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("message2event: reply to a Discord member join (who didn't join on Matrix)", async t => {
|
||||||
|
const events = await messageToEvent(data.message.reply_to_member_join, data.guild.general)
|
||||||
|
t.deepEqual(events, [{
|
||||||
|
$type: "m.room.message",
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "> PEASANT!! joined the room\n\nwhen the broke friend who we pay to bring food shows up at the medieval lord party",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<blockquote><strong>PEASANT!!</strong> joined the room</blockquote>when the broke friend who we pay to bring food shows up at the medieval lord party",
|
||||||
|
"m.mentions": {}
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("message2event: reply to a Discord member join (who did join on Matrix)", async t => {
|
||||||
|
db.prepare("INSERT INTO sim (user_id, username, sim_name, mxid) VALUES ('1461677775554478161', 'peasant321_76775', 'peasant321_76775', '@_ooye_peasant321_76775:cadence.moe')").run()
|
||||||
|
const events = await messageToEvent(data.message.reply_to_member_join, data.guild.general)
|
||||||
|
t.deepEqual(events, [{
|
||||||
|
$type: "m.room.message",
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "> PEASANT!! joined the room\n\nwhen the broke friend who we pay to bring food shows up at the medieval lord party",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: `<blockquote><a href="https://matrix.to/#/@_ooye_peasant321_76775:cadence.moe">PEASANT!!</a> joined the room</blockquote>when the broke friend who we pay to bring food shows up at the medieval lord party`,
|
||||||
|
"m.mentions": {}
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
test("message2event: simple written @mention for matrix user", async t => {
|
test("message2event: simple written @mention for matrix user", async t => {
|
||||||
const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, {
|
const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, {
|
||||||
api: {
|
api: {
|
||||||
|
|
@ -1142,6 +1168,19 @@ test("message2event: type 4 channel name change", async t => {
|
||||||
}])
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("message2event: type 12 channel follow add", async t => {
|
||||||
|
const events = await messageToEvent(data.special_message.channel_follow_add, data.guild.general)
|
||||||
|
t.deepEqual(events, [{
|
||||||
|
$type: "m.room.message",
|
||||||
|
"m.mentions": {},
|
||||||
|
msgtype: "m.emote",
|
||||||
|
body: "set this room to receive announcements from PluralKit #downtime",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "set this room to receive announcements from <strong>PluralKit #downtime</strong>",
|
||||||
|
"m.mentions": {}
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
test("message2event: thread start message reference", async t => {
|
test("message2event: thread start message reference", async t => {
|
||||||
const events = await messageToEvent(data.special_message.thread_start_context, data.guild.general, {}, {
|
const events = await messageToEvent(data.special_message.thread_start_context, data.guild.general, {}, {
|
||||||
api: {
|
api: {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ function pinsToList(pins, kstate) {
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
const result = []
|
const result = []
|
||||||
for (const pin of pins.items) {
|
for (const pin of pins.items) {
|
||||||
const eventID = select("event_message", "event_id", {message_id: pin.message.id, part: 0}).pluck().get()
|
const eventID = select("event_message", "event_id", {message_id: pin.message.id}, "ORDER BY part ASC").pluck().get()
|
||||||
if (eventID && !alreadyPinned.includes(eventID)) result.push(eventID)
|
if (eventID && !alreadyPinned.includes(eventID)) result.push(eventID)
|
||||||
}
|
}
|
||||||
result.reverse()
|
result.reverse()
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ async function _interact({guild_id, data}, {api}) {
|
||||||
// from Matrix
|
// from Matrix
|
||||||
const event = await api.getEvent(message.room_id, message.event_id)
|
const event = await api.getEvent(message.room_id, message.event_id)
|
||||||
const via = await utils.getViaServersQuery(message.room_id, api)
|
const via = await utils.getViaServersQuery(message.room_id, api)
|
||||||
|
|
||||||
const channelsInGuild = discord.guildChannelMap.get(guild_id)
|
const channelsInGuild = discord.guildChannelMap.get(guild_id)
|
||||||
assert(channelsInGuild)
|
assert(channelsInGuild)
|
||||||
const inChannels = channelsInGuild
|
const inChannels = channelsInGuild
|
||||||
|
|
@ -61,6 +62,11 @@ async function _interact({guild_id, data}, {api}) {
|
||||||
.map(/** @returns {DiscordTypes.APIGuildChannel} */ cid => discord.channels.get(cid))
|
.map(/** @returns {DiscordTypes.APIGuildChannel} */ cid => discord.channels.get(cid))
|
||||||
.sort((a, b) => webGuild._getPosition(a, discord.channels) - webGuild._getPosition(b, discord.channels))
|
.sort((a, b) => webGuild._getPosition(a, discord.channels) - webGuild._getPosition(b, discord.channels))
|
||||||
.filter(channel => from("channel_room").join("member_cache", "room_id").select("mxid").where({channel_id: channel.id, mxid: event.sender}).get())
|
.filter(channel => from("channel_room").join("member_cache", "room_id").select("mxid").where({channel_id: channel.id, mxid: event.sender}).get())
|
||||||
|
let inChannelsText = inChannels.map(c => `<#${c.id}>`).join(" • ")
|
||||||
|
if (inChannelsText.length > 1024) {
|
||||||
|
inChannelsText = `In ${inChannels.length} channels`
|
||||||
|
}
|
||||||
|
|
||||||
const matrixMember = select("member_cache", ["displayname", "avatar_url"], {room_id: message.room_id, mxid: event.sender}).get()
|
const matrixMember = select("member_cache", ["displayname", "avatar_url"], {room_id: message.room_id, mxid: event.sender}).get()
|
||||||
let name = matrixMember?.displayname || event.sender
|
let name = matrixMember?.displayname || event.sender
|
||||||
let avatar = utils.getPublicUrlForMxc(matrixMember?.avatar_url)
|
let avatar = utils.getPublicUrlForMxc(matrixMember?.avatar_url)
|
||||||
|
|
@ -98,7 +104,7 @@ async function _interact({guild_id, data}, {api}) {
|
||||||
color: 0x0dbd8b,
|
color: 0x0dbd8b,
|
||||||
fields: [{
|
fields: [{
|
||||||
name: "In Channels",
|
name: "In Channels",
|
||||||
value: inChannels.map(c => `<#${c.id}>`).join(" • ")
|
value: inChannelsText
|
||||||
}, {
|
}, {
|
||||||
name: "\u200b",
|
name: "\u200b",
|
||||||
value: idInfo
|
value: idInfo
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,394 @@ function filterTo(xs, fn) {
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedPlaintextPreviewExtensions = new Set([
|
||||||
|
"4d",
|
||||||
|
"abnf",
|
||||||
|
"accesslog",
|
||||||
|
"actionscript",
|
||||||
|
"ada",
|
||||||
|
"adoc",
|
||||||
|
"alan",
|
||||||
|
"angelscript",
|
||||||
|
"ansi",
|
||||||
|
"apache",
|
||||||
|
"apacheconf",
|
||||||
|
"applescript",
|
||||||
|
"arcade",
|
||||||
|
"arduino",
|
||||||
|
"arm",
|
||||||
|
"armasm",
|
||||||
|
"as",
|
||||||
|
"asc",
|
||||||
|
"asciidoc",
|
||||||
|
"aspectj",
|
||||||
|
"ass",
|
||||||
|
"atom",
|
||||||
|
"autohotkey",
|
||||||
|
"autoit",
|
||||||
|
"avrasm",
|
||||||
|
"awk",
|
||||||
|
"axapta",
|
||||||
|
"bash",
|
||||||
|
"basic",
|
||||||
|
"bat",
|
||||||
|
"bbcode",
|
||||||
|
"bf",
|
||||||
|
"bind",
|
||||||
|
"blade",
|
||||||
|
"bnf",
|
||||||
|
"brainfuck",
|
||||||
|
"c",
|
||||||
|
"c++",
|
||||||
|
"cal",
|
||||||
|
"capnp",
|
||||||
|
"capnproto",
|
||||||
|
"cc",
|
||||||
|
"chaos",
|
||||||
|
"chapel",
|
||||||
|
"chpl",
|
||||||
|
"cisco",
|
||||||
|
"clj",
|
||||||
|
"clojure",
|
||||||
|
"cls",
|
||||||
|
"cmake.in",
|
||||||
|
"cmake",
|
||||||
|
"cmd",
|
||||||
|
"coffee",
|
||||||
|
"coffeescript",
|
||||||
|
"console",
|
||||||
|
"coq",
|
||||||
|
"cos",
|
||||||
|
"cpc",
|
||||||
|
"cpp",
|
||||||
|
"cr",
|
||||||
|
"craftcms",
|
||||||
|
"crm",
|
||||||
|
"crmsh",
|
||||||
|
"crystal",
|
||||||
|
"cs",
|
||||||
|
"csharp",
|
||||||
|
"cshtml",
|
||||||
|
"cson",
|
||||||
|
"csp",
|
||||||
|
"css",
|
||||||
|
"csv",
|
||||||
|
"cxx",
|
||||||
|
"cypher",
|
||||||
|
"d",
|
||||||
|
"dart",
|
||||||
|
"delphi",
|
||||||
|
"dfm",
|
||||||
|
"diff",
|
||||||
|
"django",
|
||||||
|
"dns",
|
||||||
|
"docker",
|
||||||
|
"dockerfile",
|
||||||
|
"dos",
|
||||||
|
"dpr",
|
||||||
|
"dsconfig",
|
||||||
|
"dst",
|
||||||
|
"dts",
|
||||||
|
"dust",
|
||||||
|
"dylan",
|
||||||
|
"ebnf",
|
||||||
|
"elixir",
|
||||||
|
"elm",
|
||||||
|
"erl",
|
||||||
|
"erlang",
|
||||||
|
"ex",
|
||||||
|
"extempore",
|
||||||
|
"f90",
|
||||||
|
"f95",
|
||||||
|
"fix",
|
||||||
|
"fortran",
|
||||||
|
"freepascal",
|
||||||
|
"fs",
|
||||||
|
"fsharp",
|
||||||
|
"gams",
|
||||||
|
"gauss",
|
||||||
|
"gawk",
|
||||||
|
"gcode",
|
||||||
|
"gdscript",
|
||||||
|
"gemspec",
|
||||||
|
"gf",
|
||||||
|
"gherkin",
|
||||||
|
"glsl",
|
||||||
|
"gms",
|
||||||
|
"gn",
|
||||||
|
"gni",
|
||||||
|
"go",
|
||||||
|
"godot",
|
||||||
|
"golang",
|
||||||
|
"golo",
|
||||||
|
"gololang",
|
||||||
|
"gradle",
|
||||||
|
"graph",
|
||||||
|
"groovy",
|
||||||
|
"gss",
|
||||||
|
"gyp",
|
||||||
|
"h",
|
||||||
|
"h++",
|
||||||
|
"haml",
|
||||||
|
"handlebars",
|
||||||
|
"haskell",
|
||||||
|
"haxe",
|
||||||
|
"hbs",
|
||||||
|
"hcl",
|
||||||
|
"hh",
|
||||||
|
"hpp",
|
||||||
|
"hs",
|
||||||
|
"html.handlebars",
|
||||||
|
"html.hbs",
|
||||||
|
"html",
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"hx",
|
||||||
|
"hxx",
|
||||||
|
"hy",
|
||||||
|
"hylang",
|
||||||
|
"i",
|
||||||
|
"i7",
|
||||||
|
"iced",
|
||||||
|
"iecst",
|
||||||
|
"inform7",
|
||||||
|
"ini",
|
||||||
|
"ino",
|
||||||
|
"instances",
|
||||||
|
"iol",
|
||||||
|
"irb",
|
||||||
|
"irpf90",
|
||||||
|
"java",
|
||||||
|
"javascript",
|
||||||
|
"jinja",
|
||||||
|
"jolie",
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"jsp",
|
||||||
|
"jsx",
|
||||||
|
"julia-repl",
|
||||||
|
"julia",
|
||||||
|
"k",
|
||||||
|
"kaos",
|
||||||
|
"kdb",
|
||||||
|
"kotlin",
|
||||||
|
"kt",
|
||||||
|
"lasso",
|
||||||
|
"lassoscript",
|
||||||
|
"lazarus",
|
||||||
|
"ldif",
|
||||||
|
"leaf",
|
||||||
|
"lean",
|
||||||
|
"less",
|
||||||
|
"lfm",
|
||||||
|
"lisp",
|
||||||
|
"livecodeserver",
|
||||||
|
"livescript",
|
||||||
|
"ln",
|
||||||
|
"lock",
|
||||||
|
"log",
|
||||||
|
"lpr",
|
||||||
|
"ls",
|
||||||
|
"ls",
|
||||||
|
"lua",
|
||||||
|
"mak",
|
||||||
|
"make",
|
||||||
|
"makefile",
|
||||||
|
"markdown",
|
||||||
|
"mathematica",
|
||||||
|
"matlab",
|
||||||
|
"mawk",
|
||||||
|
"maxima",
|
||||||
|
"md",
|
||||||
|
"mel",
|
||||||
|
"mercury",
|
||||||
|
"mirc",
|
||||||
|
"mizar",
|
||||||
|
"mk",
|
||||||
|
"mkd",
|
||||||
|
"mkdown",
|
||||||
|
"ml",
|
||||||
|
"ml",
|
||||||
|
"mm",
|
||||||
|
"mma",
|
||||||
|
"mojolicious",
|
||||||
|
"monkey",
|
||||||
|
"moon",
|
||||||
|
"moonscript",
|
||||||
|
"mrc",
|
||||||
|
"n1ql",
|
||||||
|
"nawk",
|
||||||
|
"nc",
|
||||||
|
"never",
|
||||||
|
"nginx",
|
||||||
|
"nginxconf",
|
||||||
|
"nim",
|
||||||
|
"nimrod",
|
||||||
|
"nix",
|
||||||
|
"nsis",
|
||||||
|
"obj-c",
|
||||||
|
"obj-c++",
|
||||||
|
"objc",
|
||||||
|
"objective-c++",
|
||||||
|
"objectivec",
|
||||||
|
"ocaml",
|
||||||
|
"ocl",
|
||||||
|
"ol",
|
||||||
|
"openscad",
|
||||||
|
"osascript",
|
||||||
|
"oxygene",
|
||||||
|
"p21",
|
||||||
|
"parser3",
|
||||||
|
"pas",
|
||||||
|
"pascal",
|
||||||
|
"patch",
|
||||||
|
"pcmk",
|
||||||
|
"perl",
|
||||||
|
"pf.conf",
|
||||||
|
"pf",
|
||||||
|
"pgsql",
|
||||||
|
"php",
|
||||||
|
"php3",
|
||||||
|
"php4",
|
||||||
|
"php5",
|
||||||
|
"php6",
|
||||||
|
"php7",
|
||||||
|
"pl",
|
||||||
|
"plaintext",
|
||||||
|
"plist",
|
||||||
|
"pm",
|
||||||
|
"podspec",
|
||||||
|
"pony",
|
||||||
|
"postgres",
|
||||||
|
"postgresql",
|
||||||
|
"powershell",
|
||||||
|
"pp",
|
||||||
|
"processing",
|
||||||
|
"profile",
|
||||||
|
"prolog",
|
||||||
|
"properties",
|
||||||
|
"proto",
|
||||||
|
"protobuf",
|
||||||
|
"ps",
|
||||||
|
"ps1",
|
||||||
|
"puppet",
|
||||||
|
"py",
|
||||||
|
"pycon",
|
||||||
|
"python-repl",
|
||||||
|
"python",
|
||||||
|
"qml",
|
||||||
|
"r",
|
||||||
|
"razor-cshtml",
|
||||||
|
"razor",
|
||||||
|
"rb",
|
||||||
|
"re",
|
||||||
|
"reasonml",
|
||||||
|
"rebol",
|
||||||
|
"red-system",
|
||||||
|
"red",
|
||||||
|
"redbol",
|
||||||
|
"rf",
|
||||||
|
"rib",
|
||||||
|
"robot",
|
||||||
|
"rpm-spec",
|
||||||
|
"rpm-specfile",
|
||||||
|
"rpm",
|
||||||
|
"rs",
|
||||||
|
"rsl",
|
||||||
|
"rss",
|
||||||
|
"ruby",
|
||||||
|
"ruleslanguage",
|
||||||
|
"rust",
|
||||||
|
"sas",
|
||||||
|
"SAS",
|
||||||
|
"sc",
|
||||||
|
"scad",
|
||||||
|
"scala",
|
||||||
|
"scheme",
|
||||||
|
"sci",
|
||||||
|
"scilab",
|
||||||
|
"scl",
|
||||||
|
"scss",
|
||||||
|
"sh",
|
||||||
|
"shell",
|
||||||
|
"shexc",
|
||||||
|
"smali",
|
||||||
|
"smalltalk",
|
||||||
|
"sml",
|
||||||
|
"sol",
|
||||||
|
"solidity",
|
||||||
|
"spec",
|
||||||
|
"specfile",
|
||||||
|
"sql",
|
||||||
|
"srt",
|
||||||
|
"ssa",
|
||||||
|
"st",
|
||||||
|
"stan",
|
||||||
|
"stanfuncs",
|
||||||
|
"stata",
|
||||||
|
"step",
|
||||||
|
"stp",
|
||||||
|
"structured-text",
|
||||||
|
"styl",
|
||||||
|
"stylus",
|
||||||
|
"subunit",
|
||||||
|
"supercollider",
|
||||||
|
"svelte",
|
||||||
|
"svg",
|
||||||
|
"swift",
|
||||||
|
"tao",
|
||||||
|
"tap",
|
||||||
|
"tcl",
|
||||||
|
"terraform",
|
||||||
|
"tex",
|
||||||
|
"text",
|
||||||
|
"tf",
|
||||||
|
"thor",
|
||||||
|
"thrift",
|
||||||
|
"tk",
|
||||||
|
"toml",
|
||||||
|
"tp",
|
||||||
|
"ts",
|
||||||
|
"tsql",
|
||||||
|
"tsx",
|
||||||
|
"ttml",
|
||||||
|
"twig",
|
||||||
|
"txt",
|
||||||
|
"typescript",
|
||||||
|
"unicorn-rails-log",
|
||||||
|
"v",
|
||||||
|
"vala",
|
||||||
|
"vb",
|
||||||
|
"vba",
|
||||||
|
"vbnet",
|
||||||
|
"vbs",
|
||||||
|
"vbscript",
|
||||||
|
"verilog",
|
||||||
|
"vhdl",
|
||||||
|
"vim",
|
||||||
|
"vtt",
|
||||||
|
"wl",
|
||||||
|
"x++",
|
||||||
|
"x86asm",
|
||||||
|
"xhtml",
|
||||||
|
"xjb",
|
||||||
|
"xl",
|
||||||
|
"xml",
|
||||||
|
"xpath",
|
||||||
|
"xq",
|
||||||
|
"xquery",
|
||||||
|
"xsd",
|
||||||
|
"xsl",
|
||||||
|
"xtlang",
|
||||||
|
"xtm",
|
||||||
|
"yaml",
|
||||||
|
"yml",
|
||||||
|
"zep",
|
||||||
|
"zephir",
|
||||||
|
"zone",
|
||||||
|
"zsh"
|
||||||
|
])
|
||||||
|
|
||||||
module.exports.getPermissions = getPermissions
|
module.exports.getPermissions = getPermissions
|
||||||
module.exports.getDefaultPermissions = getDefaultPermissions
|
module.exports.getDefaultPermissions = getDefaultPermissions
|
||||||
module.exports.hasPermission = hasPermission
|
module.exports.hasPermission = hasPermission
|
||||||
|
|
@ -194,3 +582,4 @@ module.exports.timestampToSnowflakeInexact = timestampToSnowflakeInexact
|
||||||
module.exports.getPublicUrlForCdn = getPublicUrlForCdn
|
module.exports.getPublicUrlForCdn = getPublicUrlForCdn
|
||||||
module.exports.howOldUnbridgedMessage = howOldUnbridgedMessage
|
module.exports.howOldUnbridgedMessage = howOldUnbridgedMessage
|
||||||
module.exports.filterTo = filterTo
|
module.exports.filterTo = filterTo
|
||||||
|
module.exports.supportedPlaintextPreviewExtensions = supportedPlaintextPreviewExtensions
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ async function updatePins(pins, prev) {
|
||||||
const diff = diffPins.diffPins(pins, prev)
|
const diff = diffPins.diffPins(pins, prev)
|
||||||
for (const [event_id, added] of diff) {
|
for (const [event_id, added] of diff) {
|
||||||
const row = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
|
const row = from("event_message").join("message_room", "message_id").join("historical_channel_room", "historical_room_index")
|
||||||
.select("reference_channel_id", "message_id").get()
|
.select("reference_channel_id", "message_id").where({event_id}).and("ORDER BY part ASC").get()
|
||||||
if (!row) continue
|
if (!row) continue
|
||||||
if (added) {
|
if (added) {
|
||||||
discord.snow.channel.addChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message pinned on Matrix")
|
discord.snow.channel.addChannelPinnedMessage(row.reference_channel_id, row.message_id, "Message pinned on Matrix")
|
||||||
|
|
|
||||||
|
|
@ -816,16 +816,6 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
if (shouldProcessTextEvent) {
|
if (shouldProcessTextEvent) {
|
||||||
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
|
if (event.content.format === "org.matrix.custom.html" && event.content.formatted_body) {
|
||||||
let input = event.content.formatted_body
|
let input = event.content.formatted_body
|
||||||
if (perMessageProfile?.has_fallback) {
|
|
||||||
// Strip fallback elements added for clients that don't support per-message profiles.
|
|
||||||
// Deviates from recommended regexp in MSC to be less strict. Avoiding an HTML parser for performance reasons.
|
|
||||||
// ┌────A────┐ Opening HTML tag: capture tag name and stay within tag
|
|
||||||
// ┆ ┆┌─────────────B────────────┐ This text in the tag somewhere, presumably an attribute name
|
|
||||||
// ┆ ┆┆ ┆┌─C──┐ Rest of the opening tag
|
|
||||||
// ┆ ┆┆ ┆┆ ┆┌─D─┐ Tag content (no more tags allowed within)
|
|
||||||
// ┆ ┆┆ ┆┆ ┆┆ ┆┌─E──┐ Closing tag matching opening tag name
|
|
||||||
input = input.replace(/<(\w+)[^>]*\bdata-mx-profile-fallback\b[^>]*>[^<]*<\/\1>/g, "")
|
|
||||||
}
|
|
||||||
if (event.content.msgtype === "m.emote") {
|
if (event.content.msgtype === "m.emote") {
|
||||||
input = `* ${displayName} ${input}`
|
input = `* ${displayName} ${input}`
|
||||||
}
|
}
|
||||||
|
|
@ -886,8 +876,9 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
const doc = domino.createDocument(
|
const doc = domino.createDocument(
|
||||||
// DOM parsers arrange elements in the <head> and <body>. Wrapping in a custom element ensures elements are reliably arranged in a single element.
|
// DOM parsers arrange elements in the <head> and <body>. Wrapping in a custom element ensures elements are reliably arranged in a single element.
|
||||||
'<x-turndown id="turndown-root">' + input + '</x-turndown>'
|
'<x-turndown id="turndown-root">' + input + '</x-turndown>'
|
||||||
);
|
)
|
||||||
const root = doc.getElementById("turndown-root");
|
const root = doc.getElementById("turndown-root")
|
||||||
|
assert(root)
|
||||||
async function forEachNode(event, node) {
|
async function forEachNode(event, node) {
|
||||||
for (; node; node = node.nextSibling) {
|
for (; node; node = node.nextSibling) {
|
||||||
// Check written mentions
|
// Check written mentions
|
||||||
|
|
@ -903,7 +894,8 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
let preNode
|
let preNode
|
||||||
if (node.nodeType === 3 && node.nodeValue.includes("```") && (preNode = nodeIsChildOf(node, ["PRE"]))) {
|
if (node.nodeType === 3 && node.nodeValue.includes("```") && (preNode = nodeIsChildOf(node, ["PRE"]))) {
|
||||||
if (preNode.firstChild?.nodeName === "CODE") {
|
if (preNode.firstChild?.nodeName === "CODE") {
|
||||||
const ext = preNode.firstChild.className.match(/language-(\S+)/)?.[1] || "txt"
|
let ext = preNode.firstChild.className.match(/language-(\S+)/)?.[1]
|
||||||
|
if (!dUtils.supportedPlaintextPreviewExtensions.has(ext)) ext = "txt"
|
||||||
const filename = `inline_code.${ext}`
|
const filename = `inline_code.${ext}`
|
||||||
// Build the replacement <code> node
|
// Build the replacement <code> node
|
||||||
const replacementCode = doc.createElement("code")
|
const replacementCode = doc.createElement("code")
|
||||||
|
|
@ -940,6 +932,7 @@ async function eventToMessage(event, guild, channel, di) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await forEachNode(event, root)
|
await forEachNode(event, root)
|
||||||
|
if (perMessageProfile?.has_fallback) root.querySelectorAll("[data-mx-profile-fallback]").forEach(x => x.remove())
|
||||||
|
|
||||||
// SPRITE SHEET EMOJIS FEATURE: Emojis at the end of the message that we don't know about will be reuploaded as a sprite sheet.
|
// SPRITE SHEET EMOJIS FEATURE: Emojis at the end of the message that we don't know about will be reuploaded as a sprite sheet.
|
||||||
// First we need to determine which emojis are at the end.
|
// First we need to determine which emojis are at the end.
|
||||||
|
|
|
||||||
|
|
@ -1155,6 +1155,38 @@ test("event2message: code blocks are uploaded as attachments instead if they con
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("event2message: code blocks are uploaded as attachments instead if they contain incompatible backticks (use txt extension if discord does not recognise the language)", async t => {
|
||||||
|
t.deepEqual(
|
||||||
|
await eventToMessage({
|
||||||
|
type: "m.room.message",
|
||||||
|
sender: "@cadence:cadence.moe",
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: "wrong body",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: 'So if you run code like this<pre><code class="language-if">System.out.println("```");</code></pre>it should print a markdown formatted code block'
|
||||||
|
},
|
||||||
|
event_id: "$pGkWQuGVmrPNByrFELxhzI6MCBgJecr5I2J3z88Gc2s",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ensureJoined: [],
|
||||||
|
messagesToDelete: [],
|
||||||
|
messagesToEdit: [],
|
||||||
|
messagesToSend: [{
|
||||||
|
username: "cadence [they]",
|
||||||
|
content: "So if you run code like this `[inline_code.txt]` it should print a markdown formatted code block",
|
||||||
|
attachments: [{id: "0", filename: "inline_code.txt"}],
|
||||||
|
pendingFiles: [{name: "inline_code.txt", buffer: Buffer.from('System.out.println("```");')}],
|
||||||
|
avatar_url: undefined,
|
||||||
|
allowed_mentions: {
|
||||||
|
parse: ["users", "roles"]
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("event2message: code blocks are uploaded as attachments instead if they contain incompatible backticks (default to txt file extension)", async t => {
|
test("event2message: code blocks are uploaded as attachments instead if they contain incompatible backticks (default to txt file extension)", async t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
await eventToMessage({
|
await eventToMessage({
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,15 @@ function readRegistration() {
|
||||||
/** @type {import("../types").AppServiceRegistrationConfig} */ // @ts-ignore
|
/** @type {import("../types").AppServiceRegistrationConfig} */ // @ts-ignore
|
||||||
let reg = readRegistration()
|
let reg = readRegistration()
|
||||||
|
|
||||||
|
if (reg) {
|
||||||
|
fs.watch(registrationFilePath, {persistent: false}, () => {
|
||||||
|
let newReg = readRegistration()
|
||||||
|
if (newReg) {
|
||||||
|
Object.assign(reg, newReg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.registrationFilePath = registrationFilePath
|
module.exports.registrationFilePath = registrationFilePath
|
||||||
module.exports.readRegistration = readRegistration
|
module.exports.readRegistration = readRegistration
|
||||||
module.exports.getTemplateRegistration = getTemplateRegistration
|
module.exports.getTemplateRegistration = getTemplateRegistration
|
||||||
|
|
|
||||||
16
src/stdin.js
16
src/stdin.js
|
|
@ -23,6 +23,22 @@ const setPresence = sync.require("./d2m/actions/set-presence")
|
||||||
const channelWebhook = sync.require("./m2d/actions/channel-webhook")
|
const channelWebhook = sync.require("./m2d/actions/channel-webhook")
|
||||||
const guildID = "112760669178241024"
|
const guildID = "112760669178241024"
|
||||||
|
|
||||||
|
async function ping() {
|
||||||
|
const result = await api.ping().catch(e => ({ok: false, status: "net", root: e.message}))
|
||||||
|
if (result.ok) {
|
||||||
|
return "Ping OK. The homeserver and OOYE are talking to each other fine."
|
||||||
|
} else {
|
||||||
|
if (typeof result.root === "string") {
|
||||||
|
var msg = `Cannot reach homeserver: ${result.root}`
|
||||||
|
} else if (result.root.error) {
|
||||||
|
var msg = `Homeserver said: [${result.status}] ${result.root.error}`
|
||||||
|
} else {
|
||||||
|
var msg = `Homeserver said: [${result.status}] ${JSON.stringify(result.root)}`
|
||||||
|
}
|
||||||
|
return msg + "\nMatrix->Discord won't work until you fix this.\nIf your installation has recently changed, consider `npm run setup` again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
if (!passthrough.repl) {
|
if (!passthrough.repl) {
|
||||||
|
|
|
||||||
125
test/data.js
125
test/data.js
|
|
@ -19,6 +19,26 @@ module.exports = {
|
||||||
default_thread_rate_limit_per_user: 0,
|
default_thread_rate_limit_per_user: 0,
|
||||||
guild_id: "112760669178241024"
|
guild_id: "112760669178241024"
|
||||||
},
|
},
|
||||||
|
voice: {
|
||||||
|
voice_background_display: null,
|
||||||
|
version: 1774469910848,
|
||||||
|
user_limit: 0,
|
||||||
|
type: 2,
|
||||||
|
theme_color: null,
|
||||||
|
status: null,
|
||||||
|
rtc_region: null,
|
||||||
|
rate_limit_per_user: 0,
|
||||||
|
position: 0,
|
||||||
|
permission_overwrites: [],
|
||||||
|
parent_id: "805261291908104252",
|
||||||
|
nsfw: false,
|
||||||
|
name: "🍞丨[8user] Piece",
|
||||||
|
last_message_id: "1459912691098325137",
|
||||||
|
id: "1036840786093953084",
|
||||||
|
flags: 0,
|
||||||
|
bitrate: 256000,
|
||||||
|
guild_id: "112760669178241024"
|
||||||
|
},
|
||||||
updates: {
|
updates: {
|
||||||
type: 0,
|
type: 0,
|
||||||
topic: "Updates and release announcements for Out Of Your Element.",
|
topic: "Updates and release announcements for Out Of Your Element.",
|
||||||
|
|
@ -2015,6 +2035,80 @@ module.exports = {
|
||||||
tts: false
|
tts: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
reply_to_member_join: {
|
||||||
|
type: 19,
|
||||||
|
content: "when the broke friend who we pay to bring food shows up at the medieval lord party",
|
||||||
|
mentions: [],
|
||||||
|
mention_roles: [],
|
||||||
|
attachments: [],
|
||||||
|
embeds: [],
|
||||||
|
timestamp: "2026-03-30T12:11:04.443000+00:00",
|
||||||
|
edited_timestamp: null,
|
||||||
|
flags: 0,
|
||||||
|
components: [],
|
||||||
|
id: "1488148556962332692",
|
||||||
|
channel_id: "475599038536744962",
|
||||||
|
author: {
|
||||||
|
id: "576945009408999426",
|
||||||
|
username: "randomllama121",
|
||||||
|
avatar: "08510a70f957106dad1580323c40cd7a",
|
||||||
|
discriminator: "0",
|
||||||
|
public_flags: 128,
|
||||||
|
flags: 128,
|
||||||
|
banner: null,
|
||||||
|
accent_color: null,
|
||||||
|
global_name: "random :3",
|
||||||
|
avatar_decoration_data: null,
|
||||||
|
collectibles: null,
|
||||||
|
display_name_styles: null,
|
||||||
|
banner_color: null,
|
||||||
|
clan: null,
|
||||||
|
primary_guild: null
|
||||||
|
},
|
||||||
|
pinned: false,
|
||||||
|
mention_everyone: false,
|
||||||
|
tts: false,
|
||||||
|
message_reference: {
|
||||||
|
type: 0,
|
||||||
|
channel_id: "475599038536744962",
|
||||||
|
message_id: "1488146734352826478",
|
||||||
|
guild_id: "475599038536744960"
|
||||||
|
},
|
||||||
|
referenced_message: {
|
||||||
|
type: 7,
|
||||||
|
content: "",
|
||||||
|
mentions: [],
|
||||||
|
mention_roles: [],
|
||||||
|
attachments: [],
|
||||||
|
embeds: [],
|
||||||
|
timestamp: "2026-03-30T12:03:49.899000+00:00",
|
||||||
|
edited_timestamp: null,
|
||||||
|
flags: 0,
|
||||||
|
components: [],
|
||||||
|
id: "1488146734352826478",
|
||||||
|
channel_id: "475599038536744962",
|
||||||
|
author: {
|
||||||
|
id: "1461677775554478161",
|
||||||
|
username: "peasant321_76775",
|
||||||
|
avatar: null,
|
||||||
|
discriminator: "0",
|
||||||
|
public_flags: 0,
|
||||||
|
flags: 0,
|
||||||
|
banner: null,
|
||||||
|
accent_color: null,
|
||||||
|
global_name: "PEASANT!!",
|
||||||
|
avatar_decoration_data: null,
|
||||||
|
collectibles: null,
|
||||||
|
display_name_styles: null,
|
||||||
|
banner_color: null,
|
||||||
|
clan: null,
|
||||||
|
primary_guild: null
|
||||||
|
},
|
||||||
|
pinned: false,
|
||||||
|
mention_everyone: false,
|
||||||
|
tts: false
|
||||||
|
}
|
||||||
|
},
|
||||||
attachment_no_content: {
|
attachment_no_content: {
|
||||||
id: "1124628646670389348",
|
id: "1124628646670389348",
|
||||||
type: 0,
|
type: 0,
|
||||||
|
|
@ -6170,6 +6264,37 @@ module.exports = {
|
||||||
components: [],
|
components: [],
|
||||||
position: 12
|
position: 12
|
||||||
},
|
},
|
||||||
|
channel_follow_add: {
|
||||||
|
type: 12,
|
||||||
|
content: "PluralKit #downtime",
|
||||||
|
attachments: [],
|
||||||
|
embeds: [],
|
||||||
|
timestamp: "2026-03-24T23:16:04.097Z",
|
||||||
|
edited_timestamp: null,
|
||||||
|
flags: 0,
|
||||||
|
components: [],
|
||||||
|
id: "1486141581047369888",
|
||||||
|
channel_id: "1451125453082591314",
|
||||||
|
author: {
|
||||||
|
id: "154058479798059009",
|
||||||
|
username: "exaptations",
|
||||||
|
discriminator: "0",
|
||||||
|
avatar: "57b5cfe09a48a5902f2eb8fa65bb1b80",
|
||||||
|
bot: false,
|
||||||
|
flags: 0,
|
||||||
|
globalName: "Exa",
|
||||||
|
},
|
||||||
|
pinned: false,
|
||||||
|
mentions: [],
|
||||||
|
mention_roles: [],
|
||||||
|
mention_everyone: false,
|
||||||
|
tts: false,
|
||||||
|
message_reference: {
|
||||||
|
type: 0,
|
||||||
|
channel_id: "1015204661701124206",
|
||||||
|
guild_id: "466707357099884544"
|
||||||
|
}
|
||||||
|
},
|
||||||
updated_to_start_thread_from_here: {
|
updated_to_start_thread_from_here: {
|
||||||
t: "MESSAGE_UPDATE",
|
t: "MESSAGE_UPDATE",
|
||||||
s: 19,
|
s: 19,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue