Compare commits
3 commits
79bd0254f0
...
807ab899be
Author | SHA1 | Date | |
---|---|---|---|
807ab899be | |||
d81aa51787 | |||
a8f387c161 |
7 changed files with 168 additions and 3 deletions
|
@ -12,6 +12,8 @@ const api = sync.require("../../matrix/api")
|
|||
const file = sync.require("../../matrix/file")
|
||||
/** @type {import("./create-room")} */
|
||||
const createRoom = sync.require("./create-room")
|
||||
/** @type {import("../converters/expression")} */
|
||||
const expression = sync.require("../converters/expression")
|
||||
/** @type {import("../../matrix/kstate")} */
|
||||
const ks = sync.require("../../matrix/kstate")
|
||||
|
||||
|
@ -186,8 +188,29 @@ async function syncSpaceFully(guildID) {
|
|||
return spaceID
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("discord-api-types/v10").GatewayGuildEmojisUpdateDispatchData | import("discord-api-types/v10").GatewayGuildStickersUpdateDispatchData} data
|
||||
*/
|
||||
async function syncSpaceExpressions(data) {
|
||||
// No need for kstate here. Each of these maps to a single state event, which will always overwrite what was there before. I can just send the state event.
|
||||
|
||||
const spaceID = select("guild_space", "space_id", "WHERE guild_id = ?").pluck().get(data.guild_id)
|
||||
if (!spaceID) return
|
||||
|
||||
if ("emojis" in data && data.emojis.length) {
|
||||
const content = await expression.emojisToState(data.emojis)
|
||||
api.sendState(spaceID, "im.ponies.room_emotes", "moe.cadence.ooye.pack.emojis", content)
|
||||
}
|
||||
|
||||
if ("stickers" in data && data.stickers.length) {
|
||||
const content = await expression.stickersToState(data.stickers)
|
||||
api.sendState(spaceID, "im.ponies.room_emotes", "moe.cadence.ooye.pack.stickers", content)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.createSpace = createSpace
|
||||
module.exports.ensureSpace = ensureSpace
|
||||
module.exports.syncSpace = syncSpace
|
||||
module.exports.syncSpaceFully = syncSpaceFully
|
||||
module.exports.guildToKState = guildToKState
|
||||
module.exports.syncSpaceExpressions = syncSpaceExpressions
|
||||
|
|
82
d2m/converters/expression.js
Normal file
82
d2m/converters/expression.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
// @ts-check
|
||||
|
||||
const assert = require("assert").strict
|
||||
const DiscordTypes = require("discord-api-types/v10")
|
||||
|
||||
const passthrough = require("../../passthrough")
|
||||
const {discord, sync, db, select} = passthrough
|
||||
/** @type {import("../../matrix/file")} */
|
||||
const file = sync.require("../../matrix/file")
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APIEmoji[]} emojis
|
||||
*/
|
||||
async function emojisToState(emojis) {
|
||||
const result = {
|
||||
pack: {
|
||||
display_name: "Discord Emojis",
|
||||
usage: ["emoticon"] // we'll see...
|
||||
},
|
||||
images: {
|
||||
}
|
||||
}
|
||||
await Promise.all(emojis.map(emoji =>
|
||||
// the homeserver can probably cope with doing this in parallel
|
||||
file.uploadDiscordFileToMxc(file.emoji(emoji.id, emoji.animated)).then(url => {
|
||||
result.images[emoji.name] = {
|
||||
info: {
|
||||
mimetype: emoji.animated ? "image/gif" : "image/png"
|
||||
},
|
||||
url
|
||||
}
|
||||
}).catch(e => {
|
||||
if (e.data.errcode === "M_TOO_LARGE") { // Lol.
|
||||
return
|
||||
}
|
||||
console.error(`Trying to handle emoji ${emoji.name} (${emoji.id}), but...`)
|
||||
throw e
|
||||
})
|
||||
))
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DiscordTypes.APISticker[]} stickers
|
||||
*/
|
||||
async function stickersToState(stickers) {
|
||||
const result = {
|
||||
pack: {
|
||||
display_name: "Discord Stickers",
|
||||
usage: ["sticker"] // we'll see...
|
||||
},
|
||||
images: {
|
||||
}
|
||||
}
|
||||
const shortcodes = []
|
||||
await Promise.all(stickers.map(sticker =>
|
||||
// the homeserver can probably cope with doing this in parallel
|
||||
file.uploadDiscordFileToMxc(file.sticker(sticker)).then(url => {
|
||||
|
||||
/** @type {string | undefined} */
|
||||
let body = sticker.name
|
||||
if (sticker && sticker.description) body += ` - ${sticker.description}`
|
||||
if (!body) body = undefined
|
||||
|
||||
let shortcode = sticker.name.toLowerCase().replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-|-$/g, "").replace(/--+/g, "-")
|
||||
while (shortcodes.includes(shortcode)) shortcode = shortcode + "~"
|
||||
shortcodes.push(shortcode)
|
||||
|
||||
result.images[shortcodes] = {
|
||||
info: {
|
||||
mimetype: file.stickerFormat.get(sticker.format_type)?.mime || "image/png"
|
||||
},
|
||||
body,
|
||||
url
|
||||
}
|
||||
})
|
||||
))
|
||||
return result
|
||||
}
|
||||
|
||||
module.exports.emojisToState = emojisToState
|
||||
module.exports.stickersToState = stickersToState
|
|
@ -56,6 +56,18 @@ const utils = {
|
|||
}
|
||||
}
|
||||
|
||||
} else if (message.t === "GUILD_EMOJIS_UPDATE") {
|
||||
const guild = client.guilds.get(message.d.guild_id)
|
||||
if (guild) {
|
||||
guild.emojis = message.d.emojis
|
||||
}
|
||||
|
||||
} else if (message.t === "GUILD_STICKERS_UPDATE") {
|
||||
const guild = client.guilds.get(message.d.guild_id)
|
||||
if (guild) {
|
||||
guild.stickers = message.d.stickers
|
||||
}
|
||||
|
||||
} else if (message.t === "THREAD_CREATE") {
|
||||
client.channels.set(message.d.id, message.d)
|
||||
|
||||
|
@ -98,6 +110,9 @@ const utils = {
|
|||
if (message.t === "GUILD_UPDATE") {
|
||||
await eventDispatcher.onGuildUpdate(client, message.d)
|
||||
|
||||
} else if (message.t === "GUILD_EMOJIS_UPDATE" || message.t === "GUILD_STICKERS_UPDATE") {
|
||||
await eventDispatcher.onExpressionsUpdate(client, message.d)
|
||||
|
||||
} else if (message.t === "CHANNEL_UPDATE") {
|
||||
await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false)
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ module.exports = {
|
|||
console.error(`while handling this ${gatewayMessage.t} gateway event:`)
|
||||
console.dir(gatewayMessage.d, {depth: null})
|
||||
|
||||
if (gatewayMessage.t === "TYPING_START") return
|
||||
|
||||
if (Date.now() - lastReportedEvent < 5000) return
|
||||
lastReportedEvent = Date.now()
|
||||
|
||||
|
@ -81,7 +83,7 @@ module.exports = {
|
|||
async checkMissedMessages(client, guild) {
|
||||
if (guild.unavailable) return
|
||||
const bridgedChannels = select("channel_room", "channel_id").pluck().all()
|
||||
const prepared = select("event_message", "1", "WHERE message_id = ?").pluck()
|
||||
const prepared = select("event_message", "event_id", "WHERE message_id = ?").pluck()
|
||||
for (const channel of guild.channels.concat(guild.threads)) {
|
||||
if (!bridgedChannels.includes(channel.id)) continue
|
||||
if (!channel.last_message_id) continue
|
||||
|
@ -158,7 +160,7 @@ module.exports = {
|
|||
*/
|
||||
async onMessageCreate(client, message) {
|
||||
if (message.webhook_id) {
|
||||
const row = select("webhook", "1", "WHERE webhook_id = ?").pluck().get(message.webhook_id)
|
||||
const row = select("webhook", "webhook_id", "WHERE webhook_id = ?").pluck().get(message.webhook_id)
|
||||
if (row) {
|
||||
// The message was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it.
|
||||
return
|
||||
|
@ -230,5 +232,13 @@ module.exports = {
|
|||
// Discord does not send typing stopped events, so typing only stops if the timeout is reached or if the user sends their message.
|
||||
// (We have to manually stop typing on Matrix-side when the message is sent. This is part of the send action.)
|
||||
await api.sendTyping(roomID, true, mxid, 10000)
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import("./discord-client")} client
|
||||
* @param {import("discord-api-types/v10").GatewayGuildEmojisUpdateDispatchData | import("discord-api-types/v10").GatewayGuildStickersUpdateDispatchData} data
|
||||
*/
|
||||
async onExpressionsUpdate(client, data) {
|
||||
await createSpace.syncSpaceExpressions(data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class From {
|
|||
this.sql = ""
|
||||
this.cols = []
|
||||
this.using = []
|
||||
this.isPluck = false
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,6 +70,7 @@ class From {
|
|||
const r = this
|
||||
r.constructor = Pluck
|
||||
r.cols = [col]
|
||||
r.isPluck = true
|
||||
return r
|
||||
}
|
||||
|
||||
|
@ -89,7 +91,8 @@ class From {
|
|||
}
|
||||
sql += this.sql
|
||||
/** @type {U.Prepared<Pick<U.Merge<U.Models[Table]>, Col>>} */
|
||||
const prepared = db.prepare(sql)
|
||||
let prepared = db.prepare(sql)
|
||||
if (this.isPluck) prepared = prepared.pluck()
|
||||
return prepared
|
||||
}
|
||||
|
||||
|
|
31
db/orm.test.js
Normal file
31
db/orm.test.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
// @ts-check
|
||||
|
||||
const {test} = require("supertape")
|
||||
const data = require("../test/data")
|
||||
|
||||
const {db, select, from} = require("../passthrough")
|
||||
|
||||
test("orm: select: get works", t => {
|
||||
const row = select("guild_space", "guild_id", "WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
||||
t.equal(row?.guild_id, data.guild.general.id)
|
||||
})
|
||||
|
||||
test("orm: from: get works", t => {
|
||||
const row = from("guild_space").select("guild_id").and("WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
||||
t.equal(row?.guild_id, data.guild.general.id)
|
||||
})
|
||||
|
||||
test("orm: select: get pluck works", t => {
|
||||
const guildID = select("guild_space", "guild_id", "WHERE space_id = ?").pluck().get("!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
||||
t.equal(guildID, data.guild.general.id)
|
||||
})
|
||||
|
||||
test("orm: from: get pluck works", t => {
|
||||
const guildID = from("guild_space").pluck("guild_id").and("WHERE space_id = ?").get("!jjWAGMeQdNrVZSSfvz:cadence.moe")
|
||||
t.equal(guildID, data.guild.general.id)
|
||||
})
|
||||
|
||||
test("orm: from: join and pluck works", t => {
|
||||
const mxid = from("sim").join("sim_member", "mxid").and("WHERE discord_id = ? AND room_id = ?").pluck("mxid").get("771520384671416320", "!uCtjHhfGlYbVnPVlkG:cadence.moe")
|
||||
t.equal(mxid, "@_ooye_bojack_horseman:cadence.moe")
|
||||
})
|
|
@ -25,6 +25,7 @@ passthrough.select = orm.select
|
|||
const file = sync.require("../matrix/file")
|
||||
file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not allowed to upload files during testing.\nURL: ${url}`) }
|
||||
|
||||
require("../db/orm.test")
|
||||
require("../matrix/kstate.test")
|
||||
require("../matrix/api.test")
|
||||
require("../matrix/read-registration.test")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue