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")
|
const file = sync.require("../../matrix/file")
|
||||||
/** @type {import("./create-room")} */
|
/** @type {import("./create-room")} */
|
||||||
const createRoom = sync.require("./create-room")
|
const createRoom = sync.require("./create-room")
|
||||||
|
/** @type {import("../converters/expression")} */
|
||||||
|
const expression = sync.require("../converters/expression")
|
||||||
/** @type {import("../../matrix/kstate")} */
|
/** @type {import("../../matrix/kstate")} */
|
||||||
const ks = sync.require("../../matrix/kstate")
|
const ks = sync.require("../../matrix/kstate")
|
||||||
|
|
||||||
|
@ -186,8 +188,29 @@ async function syncSpaceFully(guildID) {
|
||||||
return spaceID
|
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.createSpace = createSpace
|
||||||
module.exports.ensureSpace = ensureSpace
|
module.exports.ensureSpace = ensureSpace
|
||||||
module.exports.syncSpace = syncSpace
|
module.exports.syncSpace = syncSpace
|
||||||
module.exports.syncSpaceFully = syncSpaceFully
|
module.exports.syncSpaceFully = syncSpaceFully
|
||||||
module.exports.guildToKState = guildToKState
|
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") {
|
} else if (message.t === "THREAD_CREATE") {
|
||||||
client.channels.set(message.d.id, message.d)
|
client.channels.set(message.d.id, message.d)
|
||||||
|
|
||||||
|
@ -98,6 +110,9 @@ const utils = {
|
||||||
if (message.t === "GUILD_UPDATE") {
|
if (message.t === "GUILD_UPDATE") {
|
||||||
await eventDispatcher.onGuildUpdate(client, message.d)
|
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") {
|
} else if (message.t === "CHANNEL_UPDATE") {
|
||||||
await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false)
|
await eventDispatcher.onChannelOrThreadUpdate(client, message.d, false)
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ module.exports = {
|
||||||
console.error(`while handling this ${gatewayMessage.t} gateway event:`)
|
console.error(`while handling this ${gatewayMessage.t} gateway event:`)
|
||||||
console.dir(gatewayMessage.d, {depth: null})
|
console.dir(gatewayMessage.d, {depth: null})
|
||||||
|
|
||||||
|
if (gatewayMessage.t === "TYPING_START") return
|
||||||
|
|
||||||
if (Date.now() - lastReportedEvent < 5000) return
|
if (Date.now() - lastReportedEvent < 5000) return
|
||||||
lastReportedEvent = Date.now()
|
lastReportedEvent = Date.now()
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ module.exports = {
|
||||||
async checkMissedMessages(client, guild) {
|
async checkMissedMessages(client, guild) {
|
||||||
if (guild.unavailable) return
|
if (guild.unavailable) return
|
||||||
const bridgedChannels = select("channel_room", "channel_id").pluck().all()
|
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)) {
|
for (const channel of guild.channels.concat(guild.threads)) {
|
||||||
if (!bridgedChannels.includes(channel.id)) continue
|
if (!bridgedChannels.includes(channel.id)) continue
|
||||||
if (!channel.last_message_id) continue
|
if (!channel.last_message_id) continue
|
||||||
|
@ -158,7 +160,7 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
async onMessageCreate(client, message) {
|
async onMessageCreate(client, message) {
|
||||||
if (message.webhook_id) {
|
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) {
|
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.
|
// 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
|
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.
|
// 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.)
|
// (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)
|
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.sql = ""
|
||||||
this.cols = []
|
this.cols = []
|
||||||
this.using = []
|
this.using = []
|
||||||
|
this.isPluck = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,6 +70,7 @@ class From {
|
||||||
const r = this
|
const r = this
|
||||||
r.constructor = Pluck
|
r.constructor = Pluck
|
||||||
r.cols = [col]
|
r.cols = [col]
|
||||||
|
r.isPluck = true
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +91,8 @@ class From {
|
||||||
}
|
}
|
||||||
sql += this.sql
|
sql += this.sql
|
||||||
/** @type {U.Prepared<Pick<U.Merge<U.Models[Table]>, Col>>} */
|
/** @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
|
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")
|
const file = sync.require("../matrix/file")
|
||||||
file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not allowed to upload files during testing.\nURL: ${url}`) }
|
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/kstate.test")
|
||||||
require("../matrix/api.test")
|
require("../matrix/api.test")
|
||||||
require("../matrix/read-registration.test")
|
require("../matrix/read-registration.test")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue