Compare commits
8 commits
7712bc34a6
...
1643a46812
Author | SHA1 | Date | |
---|---|---|---|
1643a46812 | |||
e9fe250211 | |||
d3d9195f72 | |||
d2c3e7eaa3 | |||
dc3a234038 | |||
e8c172a753 | |||
d41062218f | |||
58d15d205a |
15 changed files with 319 additions and 36 deletions
|
@ -17,6 +17,7 @@ const ks = sync.require("../../matrix/kstate")
|
|||
const inflightRoomCreate = new Map()
|
||||
|
||||
/**
|
||||
* Async because it gets all room state from the homeserver.
|
||||
* @param {string} roomID
|
||||
*/
|
||||
async function roomToKState(roomID) {
|
||||
|
@ -36,12 +37,17 @@ function applyKStateDiffToRoom(roomID, kstate) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {{id: string, name: string, topic?: string?}} channel
|
||||
* @param {{id: string, name: string, topic?: string?, type: number}} channel
|
||||
* @param {{id: string}} guild
|
||||
* @param {string?} customName
|
||||
*/
|
||||
function convertNameAndTopic(channel, guild, customName) {
|
||||
const convertedName = customName || channel.name;
|
||||
let channelPrefix =
|
||||
( channel.type === DiscordTypes.ChannelType.PublicThread ? "[⛓️] "
|
||||
: channel.type === DiscordTypes.ChannelType.PrivateThread ? "[🔒⛓️] "
|
||||
: channel.type === DiscordTypes.ChannelType.GuildVoice ? "[🔊] "
|
||||
: "")
|
||||
const chosenName = customName || (channelPrefix + channel.name);
|
||||
const maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : '';
|
||||
const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : '';
|
||||
const channelIDPart = `Channel ID: ${channel.id}`;
|
||||
|
@ -51,10 +57,11 @@ function convertNameAndTopic(channel, guild, customName) {
|
|||
? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}`
|
||||
: `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`;
|
||||
|
||||
return [convertedName, convertedTopic];
|
||||
return [chosenName, convertedTopic];
|
||||
}
|
||||
|
||||
/**
|
||||
* Async because it may upload the guild icon to mxc.
|
||||
* @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel
|
||||
* @param {DiscordTypes.APIGuild} guild
|
||||
*/
|
||||
|
@ -118,13 +125,21 @@ async function createRoom(channel, guild, spaceID, kstate) {
|
|||
if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id
|
||||
const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO
|
||||
|
||||
// Name and topic can be done earlier in room creation rather than in initial_state
|
||||
// https://spec.matrix.org/latest/client-server-api/#creation
|
||||
const name = kstate["m.room.name/"].name
|
||||
delete kstate["m.room.name/"]
|
||||
assert(name)
|
||||
const topic = kstate["m.room.topic/"].topic
|
||||
delete kstate["m.room.topic/"]
|
||||
assert(topic)
|
||||
|
||||
const roomID = await postApplyPowerLevels(kstate, async kstate => {
|
||||
const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null)
|
||||
const roomID = await api.createRoom({
|
||||
name: convertedName,
|
||||
topic: convertedTopic,
|
||||
preset: "private_chat",
|
||||
visibility: "private",
|
||||
name,
|
||||
topic,
|
||||
preset: "private_chat", // This is closest to what we want, but properties from kstate override it anyway
|
||||
visibility: "private", // Not shown in the room directory
|
||||
invite,
|
||||
initial_state: ks.kstateToState(kstate)
|
||||
})
|
||||
|
@ -187,10 +202,10 @@ function channelToGuild(channel) {
|
|||
3. Get kstate for channel
|
||||
4. Create room, return new ID
|
||||
|
||||
New combined flow with ensure / sync:
|
||||
Ensure + sync flow:
|
||||
1. Get IDs
|
||||
2. Does room exist?
|
||||
2.5: If room does exist AND don't need to sync: return here
|
||||
2.5: If room does exist AND wasn't asked to sync: return here
|
||||
3. Get kstate for channel
|
||||
4. Create room with kstate if room doesn't exist
|
||||
5. Get and update room state with kstate if room does exist
|
||||
|
@ -233,7 +248,7 @@ async function _syncRoom(channelID, shouldActuallySync) {
|
|||
|
||||
console.log(`[room sync] to matrix: ${channel.name}`)
|
||||
|
||||
const {spaceID, channelKState} = await channelToKState(channel, guild)
|
||||
const {spaceID, channelKState} = await channelToKState(channel, guild) // calling this in both branches because we don't want to calculate this if not syncing
|
||||
|
||||
// sync channel state to room
|
||||
const roomKState = await roomToKState(roomID)
|
||||
|
@ -248,6 +263,16 @@ async function _syncRoom(channelID, shouldActuallySync) {
|
|||
return roomID
|
||||
}
|
||||
|
||||
/** Ensures the room exists. If it doesn't, creates the room with an accurate initial state. */
|
||||
function ensureRoom(channelID) {
|
||||
return _syncRoom(channelID, false)
|
||||
}
|
||||
|
||||
/** Actually syncs. Gets all room state from the homeserver in order to diff, and uploads the icon to mxc if it has changed. */
|
||||
function syncRoom(channelID) {
|
||||
return _syncRoom(channelID, true)
|
||||
}
|
||||
|
||||
async function _unbridgeRoom(channelID) {
|
||||
/** @ts-ignore @type {DiscordTypes.APIGuildChannel} */
|
||||
const channel = discord.channels.get(channelID)
|
||||
|
@ -276,6 +301,7 @@ async function _unbridgeRoom(channelID) {
|
|||
|
||||
|
||||
/**
|
||||
* Async because it gets all space state from the homeserver, then if necessary sends one state event back.
|
||||
* @param {DiscordTypes.APIGuildTextChannel} channel
|
||||
* @param {string} spaceID
|
||||
* @param {string} roomID
|
||||
|
@ -298,14 +324,6 @@ async function _syncSpaceMember(channel, spaceID, roomID) {
|
|||
return applyKStateDiffToRoom(spaceID, spaceDiff)
|
||||
}
|
||||
|
||||
function ensureRoom(channelID) {
|
||||
return _syncRoom(channelID, false)
|
||||
}
|
||||
|
||||
function syncRoom(channelID) {
|
||||
return _syncRoom(channelID, true)
|
||||
}
|
||||
|
||||
async function createAllForGuild(guildID) {
|
||||
const channelIDs = discord.guildChannelMap.get(guildID)
|
||||
assert.ok(channelIDs)
|
||||
|
|
|
@ -14,28 +14,49 @@ test("channel2room: general", async t => {
|
|||
|
||||
test("convertNameAndTopic: custom name and topic", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, "hauntings"),
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),
|
||||
["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: custom name, no topic", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"),
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, "hauntings"),
|
||||
["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: original name and topic", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null),
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, null),
|
||||
["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: original name, no topic", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null),
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, null),
|
||||
["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: public thread icon", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 11}, {id: "456"}, null),
|
||||
["[⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: private thread icon", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 12}, {id: "456"}, null),
|
||||
["[🔒⛓️] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
||||
test("convertNameAndTopic: voice channel icon", t => {
|
||||
t.deepEqual(
|
||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 2}, {id: "456"}, null),
|
||||
["[🔊] the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @ts-check
|
||||
|
||||
const assert = require("assert")
|
||||
const assert = require("assert").strict
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
|
@ -84,11 +84,31 @@ async function syncSpace(guildID) {
|
|||
|
||||
console.log(`[space sync] to matrix: ${guild.name}`)
|
||||
|
||||
// sync channel state to room
|
||||
// sync guild state to space
|
||||
const spaceKState = await createRoom.roomToKState(spaceID)
|
||||
const spaceDiff = ks.diffKState(spaceKState, guildKState)
|
||||
await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff)
|
||||
|
||||
// guild icon was changed, so room avatars need to be updated as well as the space ones
|
||||
// doing it this way rather than calling syncRoom for great efficiency gains
|
||||
const newAvatarState = spaceDiff["m.room.avatar/"]
|
||||
if (guild.icon && newAvatarState?.url) {
|
||||
// don't try to update rooms with custom avatars though
|
||||
const roomsWithCustomAvatars = db.prepare("SELECT room_id FROM channel_room WHERE custom_avatar IS NOT NULL").pluck().all()
|
||||
|
||||
const childRooms = ks.kstateToState(spaceKState).filter(({type, state_key, content}) => {
|
||||
return type === "m.space.child" && "via" in content && roomsWithCustomAvatars.includes(state_key)
|
||||
}).map(({state_key}) => state_key)
|
||||
|
||||
for (const roomID of childRooms) {
|
||||
const avatarEventContent = await api.getStateEvent(roomID, "m.room.avatar", "")
|
||||
if (avatarEventContent.url !== newAvatarState.url) {
|
||||
await api.sendState(roomID, "m.room.avatar", "", newAvatarState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return spaceID
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,27 @@ const {_memberToStateContent} = require("./register-user")
|
|||
const {test} = require("supertape")
|
||||
const testData = require("../../test/data")
|
||||
|
||||
test("member2state: general", async t => {
|
||||
test("member2state: without member nick or avatar", async t => {
|
||||
t.deepEqual(
|
||||
await _memberToStateContent(testData.member.kumaccino.user, testData.member.kumaccino, testData.guild.general.id),
|
||||
{
|
||||
avatar_url: "mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL",
|
||||
displayname: "kumaccino",
|
||||
membership: "join",
|
||||
"moe.cadence.ooye.member": {
|
||||
avatar: "/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024"
|
||||
},
|
||||
"uk.half-shot.discord.member": {
|
||||
bot: false,
|
||||
displayColor: 10206929,
|
||||
id: "113340068197859328",
|
||||
username: "@kumaccino"
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test("member2state: with member nick and avatar", async t => {
|
||||
t.deepEqual(
|
||||
await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id),
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ function* generateLocalpartAlternatives(preferences) {
|
|||
let i = 2
|
||||
while (true) {
|
||||
yield best + (i++)
|
||||
/* c8 ignore next */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +70,7 @@ function userToSimName(user) {
|
|||
for (const suggestion of generateLocalpartAlternatives(preferences)) {
|
||||
if (!matches.includes(suggestion)) return suggestion
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`)
|
||||
}
|
||||
|
||||
|
|
142
d2m/discord-command-handler.js
Normal file
142
d2m/discord-command-handler.js
Normal file
|
@ -0,0 +1,142 @@
|
|||
// @ts-check
|
||||
|
||||
const assert = require("assert").strict
|
||||
const util = require("util")
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
const {discord, sync, db} = require("../passthrough")
|
||||
/** @type {import("../matrix/api")}) */
|
||||
const api = sync.require("../matrix/api")
|
||||
/** @type {import("../matrix/file")} */
|
||||
const file = sync.require("../matrix/file")
|
||||
|
||||
const PREFIX = "//"
|
||||
|
||||
let buttons = []
|
||||
|
||||
/**
|
||||
* @param {string} channelID where to add the button
|
||||
* @param {string} messageID where to add the button
|
||||
* @param {string} emoji emoji to add as a button
|
||||
* @param {string} userID only listen for responses from this user
|
||||
* @returns {Promise<import("discord-api-types/v10").GatewayMessageReactionAddDispatchData>}
|
||||
*/
|
||||
async function addButton(channelID, messageID, emoji, userID) {
|
||||
await discord.snow.channel.createReaction(channelID, messageID, emoji)
|
||||
return new Promise(resolve => {
|
||||
buttons.push({channelID, messageID, userID, resolve, created: Date.now()})
|
||||
})
|
||||
}
|
||||
|
||||
// Clear out old buttons every so often to free memory
|
||||
setInterval(() => {
|
||||
const now = Date.now()
|
||||
buttons = buttons.filter(b => now - b.created < 2*60*60*1000)
|
||||
}, 10*60*1000)
|
||||
|
||||
/** @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data */
|
||||
function onReactionAdd(data) {
|
||||
const button = buttons.find(b => b.channelID === data.channel_id && b.messageID === data.message_id && b.userID === data.user_id)
|
||||
if (button) {
|
||||
buttons = buttons.filter(b => b !== button) // remove button data so it can't be clicked again
|
||||
button.resolve(data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback CommandExecute
|
||||
* @param {DiscordTypes.GatewayMessageCreateDispatchData} message
|
||||
* @param {DiscordTypes.APIGuildTextChannel} channel
|
||||
* @param {DiscordTypes.APIGuild} guild
|
||||
* @param {Partial<DiscordTypes.RESTPostAPIChannelMessageJSONBody>} [ctx]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Command
|
||||
* @property {string[]} aliases
|
||||
* @property {(message: DiscordTypes.GatewayMessageCreateDispatchData, channel: DiscordTypes.APIGuildTextChannel, guild: DiscordTypes.APIGuild) => Promise<any>} execute
|
||||
*/
|
||||
|
||||
/** @param {CommandExecute} execute */
|
||||
function replyctx(execute) {
|
||||
/** @type {CommandExecute} */
|
||||
return function(message, channel, guild, ctx = {}) {
|
||||
ctx.message_reference = {
|
||||
message_id: message.id,
|
||||
channel_id: channel.id,
|
||||
guild_id: guild.id,
|
||||
fail_if_not_exists: false
|
||||
}
|
||||
return execute(message, channel, guild, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Command[]} */
|
||||
const commands = [{
|
||||
aliases: ["icon", "avatar", "roomicon", "roomavatar", "channelicon", "channelavatar"],
|
||||
execute: replyctx(
|
||||
async (message, channel, guild, ctx) => {
|
||||
// Guard
|
||||
const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channel.id)
|
||||
if (!roomID) return discord.snow.channel.createMessage(channel.id, {
|
||||
...ctx,
|
||||
content: "This channel isn't bridged to the other side."
|
||||
})
|
||||
|
||||
// Current avatar
|
||||
const avatarEvent = await api.getStateEvent(roomID, "m.room.avatar", "")
|
||||
const avatarURLParts = avatarEvent?.url.match(/^mxc:\/\/([^/]+)\/(\w+)$/)
|
||||
let currentAvatarMessage =
|
||||
( avatarURLParts ? `Current room-specific avatar: https://matrix.cadence.moe/_matrix/media/r0/download/${avatarURLParts[1]}/${avatarURLParts[2]}`
|
||||
: "No avatar. Now's your time to strike. Use `//icon` again with a link or upload to set the room-specific avatar.")
|
||||
|
||||
// Next potential avatar
|
||||
const nextAvatarURL = message.attachments.find(a => a.content_type?.startsWith("image/"))?.url || message.content.match(/https?:\/\/[^ ]+\.[^ ]+\.(?:png|jpg|jpeg|webp)\b/)?.[0]
|
||||
let nextAvatarMessage =
|
||||
( nextAvatarURL ? `\nYou want to set it to: ${nextAvatarURL}\nHit ✅ to make it happen.`
|
||||
: "")
|
||||
|
||||
const sent = await discord.snow.channel.createMessage(channel.id, {
|
||||
...ctx,
|
||||
content: currentAvatarMessage + nextAvatarMessage
|
||||
})
|
||||
|
||||
if (nextAvatarURL) {
|
||||
addButton(channel.id, sent.id, "✅", message.author.id).then(async data => {
|
||||
const mxcUrl = await file.uploadDiscordFileToMxc(nextAvatarURL)
|
||||
await api.sendState(roomID, "m.room.avatar", "", {
|
||||
url: mxcUrl
|
||||
})
|
||||
db.prepare("UPDATE channel_room SET custom_avatar = ? WHERE channel_id = ?").run(mxcUrl, channel.id)
|
||||
await discord.snow.channel.createMessage(channel.id, {
|
||||
...ctx,
|
||||
content: "Your creation is unleashed. Any complaints will be redirected to Grelbo."
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}, {
|
||||
aliases: ["invite"],
|
||||
execute: replyctx(
|
||||
async (message, channel, guild, ctx) => {
|
||||
return discord.snow.channel.createMessage(channel.id, {
|
||||
...ctx,
|
||||
content: "This command isn't implemented yet."
|
||||
})
|
||||
}
|
||||
)
|
||||
}]
|
||||
|
||||
/** @type {CommandExecute} */
|
||||
async function execute(message, channel, guild) {
|
||||
if (!message.content.startsWith(PREFIX)) return
|
||||
const words = message.content.slice(PREFIX.length).split(" ")
|
||||
const commandName = words[0]
|
||||
const command = commands.find(c => c.aliases.includes(commandName))
|
||||
if (!command) return
|
||||
|
||||
await command.execute(message, channel, guild)
|
||||
}
|
||||
|
||||
module.exports.execute = execute
|
||||
module.exports.onReactionAdd = onReactionAdd
|
|
@ -18,6 +18,8 @@ const createRoom = sync.require("./actions/create-room")
|
|||
const createSpace = sync.require("./actions/create-space")
|
||||
/** @type {import("../matrix/api")}) */
|
||||
const api = sync.require("../matrix/api")
|
||||
/** @type {import("./discord-command-handler")}) */
|
||||
const discordCommandHandler = sync.require("./discord-command-handler")
|
||||
|
||||
let lastReportedEvent = 0
|
||||
|
||||
|
@ -156,7 +158,9 @@ module.exports = {
|
|||
if (!channel.guild_id) return // Nothing we can do in direct messages.
|
||||
const guild = client.guilds.get(channel.guild_id)
|
||||
if (!isGuildAllowed(guild.id)) return
|
||||
await sendMessage.sendMessage(message, guild)
|
||||
|
||||
await sendMessage.sendMessage(message, guild),
|
||||
await discordCommandHandler.execute(message, channel, guild)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -191,6 +195,7 @@ module.exports = {
|
|||
*/
|
||||
async onReactionAdd(client, data) {
|
||||
if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix.
|
||||
discordCommandHandler.onReactionAdd(data)
|
||||
if (data.emoji.id !== null) return // TODO: image emoji reactions
|
||||
await addReaction.addReaction(data)
|
||||
},
|
||||
|
|
|
@ -93,6 +93,7 @@ INSERT INTO file (discord_url, mxc_url) VALUES
|
|||
('https://cdn.discordapp.com/attachments/112760669178241024/1141501302497615912/piper_2.png', 'mxc://cadence.moe/KQYdXKRcHWjDYDLPkTOOWOjA'),
|
||||
('https://cdn.discordapp.com/attachments/112760669178241024/1128084851023675515/RDT_20230704_0936184915846675925224905.jpg', 'mxc://cadence.moe/WlAbFSiNRIHPDEwKdyPeGywa'),
|
||||
('https://cdn.discordapp.com/guilds/112760669178241024/users/134826546694193153/avatars/38dd359aa12bcd52dd3164126c587f8c.png?size=1024', 'mxc://cadence.moe/rfemHmAtcprjLEiPiEuzPhpl'),
|
||||
('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF');
|
||||
('https://cdn.discordapp.com/icons/112760669178241024/a_f83622e09ead74f0c5c527fe241f8f8c.png?size=1024', 'mxc://cadence.moe/zKXGZhmImMHuGQZWJEFKJbsF'),
|
||||
('https://cdn.discordapp.com/avatars/113340068197859328/b48302623a12bc7c59a71328f72ccb39.png?size=1024', 'mxc://cadence.moe/UpAeIqeclhKfeiZNdIWNcXXL');
|
||||
|
||||
COMMIT;
|
||||
|
|
|
@ -32,7 +32,7 @@ function eventToMessage(event) {
|
|||
})
|
||||
} else if (event.content.msgtype === "m.emote") {
|
||||
messages.push({
|
||||
content: `*${displayName} ${event.content.body}*`,
|
||||
content: `\* _${displayName} ${event.content.body}_`,
|
||||
username: displayName,
|
||||
avatar_url: avatarURL
|
||||
})
|
||||
|
|
|
@ -190,6 +190,7 @@ module.exports.inviteToRoom = inviteToRoom
|
|||
module.exports.leaveRoom = leaveRoom
|
||||
module.exports.getEvent = getEvent
|
||||
module.exports.getAllState = getAllState
|
||||
module.exports.getStateEvent = getStateEvent
|
||||
module.exports.getJoinedMembers = getJoinedMembers
|
||||
module.exports.sendState = sendState
|
||||
module.exports.sendEvent = sendEvent
|
||||
|
|
|
@ -27,15 +27,15 @@ async function uploadDiscordFileToMxc(path) {
|
|||
}
|
||||
|
||||
// Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution
|
||||
let existing = inflight.get(url)
|
||||
if (typeof existing === "string") {
|
||||
return existing
|
||||
const existingInflight = inflight.get(url)
|
||||
if (existingInflight) {
|
||||
return existingInflight
|
||||
}
|
||||
|
||||
// Has this file already been uploaded in the past? Grab the existing copy from the database.
|
||||
existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url)
|
||||
if (typeof existing === "string") {
|
||||
return existing
|
||||
const existingFromDb = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url)
|
||||
if (typeof existingFromDb === "string") {
|
||||
return existingFromDb
|
||||
}
|
||||
|
||||
// Download from Discord
|
||||
|
|
12
matrix/txnid.test.js
Normal file
12
matrix/txnid.test.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// @ts-check
|
||||
|
||||
const {test} = require("supertape")
|
||||
const txnid = require("./txnid")
|
||||
|
||||
test("txnid: generates different values each run", t => {
|
||||
const one = txnid.makeTxnId()
|
||||
t.ok(one)
|
||||
const two = txnid.makeTxnId()
|
||||
t.ok(two)
|
||||
t.notEqual(two, one)
|
||||
})
|
1
stdin.js
1
stdin.js
|
@ -12,6 +12,7 @@ const createRoom = sync.require("./d2m/actions/create-room")
|
|||
const registerUser = sync.require("./d2m/actions/register-user")
|
||||
const mreq = sync.require("./matrix/mreq")
|
||||
const api = sync.require("./matrix/api")
|
||||
const file = sync.require("./matrix/file")
|
||||
const sendEvent = sync.require("./m2d/actions/send-event")
|
||||
const eventDispatcher = sync.require("./d2m/event-dispatcher")
|
||||
const ks = sync.require("./matrix/kstate")
|
||||
|
|
40
test/data.js
40
test/data.js
|
@ -98,6 +98,46 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
member: {
|
||||
kumaccino: {
|
||||
avatar: null,
|
||||
communication_disabled_until: null,
|
||||
flags: 0,
|
||||
joined_at: "2015-11-11T09:55:40.321000+00:00",
|
||||
nick: null,
|
||||
pending: false,
|
||||
premium_since: null,
|
||||
roles: [
|
||||
"112767366235959296", "118924814567211009",
|
||||
"199995902742626304", "204427286542417920",
|
||||
"222168467627835392", "238028326281805825",
|
||||
"259806643414499328", "265239342648131584",
|
||||
"271173313575780353", "287733611912757249",
|
||||
"225744901915148298", "305775031223320577",
|
||||
"318243902521868288", "348651574924541953",
|
||||
"349185088157777920", "378402925128712193",
|
||||
"392141548932038658", "393912152173576203",
|
||||
"482860581670486028", "495384759074160642",
|
||||
"638988388740890635", "373336013109461013",
|
||||
"530220455085473813", "454567553738473472",
|
||||
"790724320824655873", "1040735082610167858",
|
||||
"695946570482450442", "849737964090556488"
|
||||
],
|
||||
user: {
|
||||
id: "113340068197859328",
|
||||
username: "kumaccino",
|
||||
avatar: "b48302623a12bc7c59a71328f72ccb39",
|
||||
discriminator: "0",
|
||||
public_flags: 128,
|
||||
flags: 128,
|
||||
banner: null,
|
||||
accent_color: 10206929,
|
||||
global_name: "kumaccino",
|
||||
avatar_decoration_data: null,
|
||||
banner_color: "#9bbed1"
|
||||
},
|
||||
mute: false,
|
||||
deaf: false
|
||||
},
|
||||
sheep: {
|
||||
avatar: "38dd359aa12bcd52dd3164126c587f8c",
|
||||
communication_disabled_until: null,
|
||||
|
|
|
@ -20,6 +20,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
|||
require("../matrix/kstate.test")
|
||||
require("../matrix/api.test")
|
||||
require("../matrix/read-registration.test")
|
||||
require("../matrix/txnid.test")
|
||||
require("../d2m/converters/message-to-event.test")
|
||||
require("../d2m/converters/message-to-event.embeds.test")
|
||||
require("../d2m/converters/edit-to-changes.test")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue