Compare commits
No commits in common. "1643a46812ed5b96f568e9712ea3bd64750fbf3e" and "7712bc34a6081a62cd06e2b046f3ba8869c44aa7" have entirely different histories.
1643a46812
...
7712bc34a6
15 changed files with 36 additions and 319 deletions
|
@ -17,7 +17,6 @@ const ks = sync.require("../../matrix/kstate")
|
||||||
const inflightRoomCreate = new Map()
|
const inflightRoomCreate = new Map()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async because it gets all room state from the homeserver.
|
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
*/
|
*/
|
||||||
async function roomToKState(roomID) {
|
async function roomToKState(roomID) {
|
||||||
|
@ -37,17 +36,12 @@ function applyKStateDiffToRoom(roomID, kstate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{id: string, name: string, topic?: string?, type: number}} channel
|
* @param {{id: string, name: string, topic?: string?}} channel
|
||||||
* @param {{id: string}} guild
|
* @param {{id: string}} guild
|
||||||
* @param {string?} customName
|
* @param {string?} customName
|
||||||
*/
|
*/
|
||||||
function convertNameAndTopic(channel, guild, customName) {
|
function convertNameAndTopic(channel, guild, customName) {
|
||||||
let channelPrefix =
|
const convertedName = customName || channel.name;
|
||||||
( 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 maybeTopicWithPipe = channel.topic ? ` | ${channel.topic}` : '';
|
||||||
const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : '';
|
const maybeTopicWithNewlines = channel.topic ? `${channel.topic}\n\n` : '';
|
||||||
const channelIDPart = `Channel ID: ${channel.id}`;
|
const channelIDPart = `Channel ID: ${channel.id}`;
|
||||||
|
@ -57,11 +51,10 @@ function convertNameAndTopic(channel, guild, customName) {
|
||||||
? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}`
|
? `#${channel.name}${maybeTopicWithPipe}\n\n${channelIDPart}\n${guildIDPart}`
|
||||||
: `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`;
|
: `${maybeTopicWithNewlines}${channelIDPart}\n${guildIDPart}`;
|
||||||
|
|
||||||
return [chosenName, convertedTopic];
|
return [convertedName, convertedTopic];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async because it may upload the guild icon to mxc.
|
|
||||||
* @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel
|
* @param {DiscordTypes.APIGuildTextChannel | DiscordTypes.APIThreadChannel} channel
|
||||||
* @param {DiscordTypes.APIGuild} guild
|
* @param {DiscordTypes.APIGuild} guild
|
||||||
*/
|
*/
|
||||||
|
@ -125,21 +118,13 @@ async function createRoom(channel, guild, spaceID, kstate) {
|
||||||
if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id
|
if (channel.type === DiscordTypes.ChannelType.PublicThread) threadParent = channel.parent_id
|
||||||
const invite = threadParent ? [] : ["@cadence:cadence.moe"] // TODO
|
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 roomID = await postApplyPowerLevels(kstate, async kstate => {
|
||||||
|
const [convertedName, convertedTopic] = convertNameAndTopic(channel, guild, null)
|
||||||
const roomID = await api.createRoom({
|
const roomID = await api.createRoom({
|
||||||
name,
|
name: convertedName,
|
||||||
topic,
|
topic: convertedTopic,
|
||||||
preset: "private_chat", // This is closest to what we want, but properties from kstate override it anyway
|
preset: "private_chat",
|
||||||
visibility: "private", // Not shown in the room directory
|
visibility: "private",
|
||||||
invite,
|
invite,
|
||||||
initial_state: ks.kstateToState(kstate)
|
initial_state: ks.kstateToState(kstate)
|
||||||
})
|
})
|
||||||
|
@ -202,10 +187,10 @@ function channelToGuild(channel) {
|
||||||
3. Get kstate for channel
|
3. Get kstate for channel
|
||||||
4. Create room, return new ID
|
4. Create room, return new ID
|
||||||
|
|
||||||
Ensure + sync flow:
|
New combined flow with ensure / sync:
|
||||||
1. Get IDs
|
1. Get IDs
|
||||||
2. Does room exist?
|
2. Does room exist?
|
||||||
2.5: If room does exist AND wasn't asked to sync: return here
|
2.5: If room does exist AND don't need to sync: return here
|
||||||
3. Get kstate for channel
|
3. Get kstate for channel
|
||||||
4. Create room with kstate if room doesn't exist
|
4. Create room with kstate if room doesn't exist
|
||||||
5. Get and update room state with kstate if room does exist
|
5. Get and update room state with kstate if room does exist
|
||||||
|
@ -248,7 +233,7 @@ async function _syncRoom(channelID, shouldActuallySync) {
|
||||||
|
|
||||||
console.log(`[room sync] to matrix: ${channel.name}`)
|
console.log(`[room sync] to matrix: ${channel.name}`)
|
||||||
|
|
||||||
const {spaceID, channelKState} = await channelToKState(channel, guild) // calling this in both branches because we don't want to calculate this if not syncing
|
const {spaceID, channelKState} = await channelToKState(channel, guild)
|
||||||
|
|
||||||
// sync channel state to room
|
// sync channel state to room
|
||||||
const roomKState = await roomToKState(roomID)
|
const roomKState = await roomToKState(roomID)
|
||||||
|
@ -263,16 +248,6 @@ async function _syncRoom(channelID, shouldActuallySync) {
|
||||||
return roomID
|
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) {
|
async function _unbridgeRoom(channelID) {
|
||||||
/** @ts-ignore @type {DiscordTypes.APIGuildChannel} */
|
/** @ts-ignore @type {DiscordTypes.APIGuildChannel} */
|
||||||
const channel = discord.channels.get(channelID)
|
const channel = discord.channels.get(channelID)
|
||||||
|
@ -301,7 +276,6 @@ 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 {DiscordTypes.APIGuildTextChannel} channel
|
||||||
* @param {string} spaceID
|
* @param {string} spaceID
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
|
@ -324,6 +298,14 @@ async function _syncSpaceMember(channel, spaceID, roomID) {
|
||||||
return applyKStateDiffToRoom(spaceID, spaceDiff)
|
return applyKStateDiffToRoom(spaceID, spaceDiff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureRoom(channelID) {
|
||||||
|
return _syncRoom(channelID, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncRoom(channelID) {
|
||||||
|
return _syncRoom(channelID, true)
|
||||||
|
}
|
||||||
|
|
||||||
async function createAllForGuild(guildID) {
|
async function createAllForGuild(guildID) {
|
||||||
const channelIDs = discord.guildChannelMap.get(guildID)
|
const channelIDs = discord.guildChannelMap.get(guildID)
|
||||||
assert.ok(channelIDs)
|
assert.ok(channelIDs)
|
||||||
|
|
|
@ -14,49 +14,28 @@ test("channel2room: general", async t => {
|
||||||
|
|
||||||
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:"}, {id: "456"}, "hauntings"),
|
||||||
["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
["hauntings", "#the-twilight-zone | Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("convertNameAndTopic: custom name, no topic", t => {
|
test("convertNameAndTopic: custom name, no topic", t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, "hauntings"),
|
_convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, "hauntings"),
|
||||||
["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"]
|
["hauntings", "#the-twilight-zone\n\nChannel ID: 123\nGuild ID: 456"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("convertNameAndTopic: original name and topic", t => {
|
test("convertNameAndTopic: original name and topic", t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, null),
|
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:"}, {id: "456"}, null),
|
||||||
["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
["the-twilight-zone", "Spooky stuff here. :ghost:\n\nChannel ID: 123\nGuild ID: 456"]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("convertNameAndTopic: original name, no topic", t => {
|
test("convertNameAndTopic: original name, no topic", t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
_convertNameAndTopic({id: "123", name: "the-twilight-zone", type: 0}, {id: "456"}, null),
|
_convertNameAndTopic({id: "123", name: "the-twilight-zone"}, {id: "456"}, null),
|
||||||
["the-twilight-zone", "Channel ID: 123\nGuild ID: 456"]
|
["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
|
// @ts-check
|
||||||
|
|
||||||
const assert = require("assert").strict
|
const assert = require("assert")
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
|
||||||
const passthrough = require("../../passthrough")
|
const passthrough = require("../../passthrough")
|
||||||
|
@ -84,31 +84,11 @@ async function syncSpace(guildID) {
|
||||||
|
|
||||||
console.log(`[space sync] to matrix: ${guild.name}`)
|
console.log(`[space sync] to matrix: ${guild.name}`)
|
||||||
|
|
||||||
// sync guild state to space
|
// sync channel state to room
|
||||||
const spaceKState = await createRoom.roomToKState(spaceID)
|
const spaceKState = await createRoom.roomToKState(spaceID)
|
||||||
const spaceDiff = ks.diffKState(spaceKState, guildKState)
|
const spaceDiff = ks.diffKState(spaceKState, guildKState)
|
||||||
await createRoom.applyKStateDiffToRoom(spaceID, spaceDiff)
|
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
|
return spaceID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,27 +3,7 @@ const {_memberToStateContent} = require("./register-user")
|
||||||
const {test} = require("supertape")
|
const {test} = require("supertape")
|
||||||
const testData = require("../../test/data")
|
const testData = require("../../test/data")
|
||||||
|
|
||||||
test("member2state: without member nick or avatar", async t => {
|
test("member2state: general", 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(
|
t.deepEqual(
|
||||||
await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id),
|
await _memberToStateContent(testData.member.sheep.user, testData.member.sheep, testData.guild.general.id),
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,7 +39,6 @@ function* generateLocalpartAlternatives(preferences) {
|
||||||
let i = 2
|
let i = 2
|
||||||
while (true) {
|
while (true) {
|
||||||
yield best + (i++)
|
yield best + (i++)
|
||||||
/* c8 ignore next */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ function userToSimName(user) {
|
||||||
for (const suggestion of generateLocalpartAlternatives(preferences)) {
|
for (const suggestion of generateLocalpartAlternatives(preferences)) {
|
||||||
if (!matches.includes(suggestion)) return suggestion
|
if (!matches.includes(suggestion)) return suggestion
|
||||||
}
|
}
|
||||||
/* c8 ignore next */
|
|
||||||
throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`)
|
throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
// @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,8 +18,6 @@ const createRoom = sync.require("./actions/create-room")
|
||||||
const createSpace = sync.require("./actions/create-space")
|
const createSpace = sync.require("./actions/create-space")
|
||||||
/** @type {import("../matrix/api")}) */
|
/** @type {import("../matrix/api")}) */
|
||||||
const api = sync.require("../matrix/api")
|
const api = sync.require("../matrix/api")
|
||||||
/** @type {import("./discord-command-handler")}) */
|
|
||||||
const discordCommandHandler = sync.require("./discord-command-handler")
|
|
||||||
|
|
||||||
let lastReportedEvent = 0
|
let lastReportedEvent = 0
|
||||||
|
|
||||||
|
@ -158,9 +156,7 @@ module.exports = {
|
||||||
if (!channel.guild_id) return // Nothing we can do in direct messages.
|
if (!channel.guild_id) return // Nothing we can do in direct messages.
|
||||||
const guild = client.guilds.get(channel.guild_id)
|
const guild = client.guilds.get(channel.guild_id)
|
||||||
if (!isGuildAllowed(guild.id)) return
|
if (!isGuildAllowed(guild.id)) return
|
||||||
|
await sendMessage.sendMessage(message, guild)
|
||||||
await sendMessage.sendMessage(message, guild),
|
|
||||||
await discordCommandHandler.execute(message, channel, guild)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,7 +191,6 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
async onReactionAdd(client, data) {
|
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.
|
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
|
if (data.emoji.id !== null) return // TODO: image emoji reactions
|
||||||
await addReaction.addReaction(data)
|
await addReaction.addReaction(data)
|
||||||
},
|
},
|
||||||
|
|
|
@ -93,7 +93,6 @@ 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/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/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/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;
|
COMMIT;
|
||||||
|
|
|
@ -32,7 +32,7 @@ function eventToMessage(event) {
|
||||||
})
|
})
|
||||||
} else if (event.content.msgtype === "m.emote") {
|
} else if (event.content.msgtype === "m.emote") {
|
||||||
messages.push({
|
messages.push({
|
||||||
content: `\* _${displayName} ${event.content.body}_`,
|
content: `*${displayName} ${event.content.body}*`,
|
||||||
username: displayName,
|
username: displayName,
|
||||||
avatar_url: avatarURL
|
avatar_url: avatarURL
|
||||||
})
|
})
|
||||||
|
|
|
@ -190,7 +190,6 @@ module.exports.inviteToRoom = inviteToRoom
|
||||||
module.exports.leaveRoom = leaveRoom
|
module.exports.leaveRoom = leaveRoom
|
||||||
module.exports.getEvent = getEvent
|
module.exports.getEvent = getEvent
|
||||||
module.exports.getAllState = getAllState
|
module.exports.getAllState = getAllState
|
||||||
module.exports.getStateEvent = getStateEvent
|
|
||||||
module.exports.getJoinedMembers = getJoinedMembers
|
module.exports.getJoinedMembers = getJoinedMembers
|
||||||
module.exports.sendState = sendState
|
module.exports.sendState = sendState
|
||||||
module.exports.sendEvent = sendEvent
|
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
|
// Are we uploading this file RIGHT NOW? Return the same inflight promise with the same resolution
|
||||||
const existingInflight = inflight.get(url)
|
let existing = inflight.get(url)
|
||||||
if (existingInflight) {
|
if (typeof existing === "string") {
|
||||||
return existingInflight
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has this file already been uploaded in the past? Grab the existing copy from the database.
|
// Has this file already been uploaded in the past? Grab the existing copy from the database.
|
||||||
const existingFromDb = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url)
|
existing = db.prepare("SELECT mxc_url FROM file WHERE discord_url = ?").pluck().get(url)
|
||||||
if (typeof existingFromDb === "string") {
|
if (typeof existing === "string") {
|
||||||
return existingFromDb
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download from Discord
|
// Download from Discord
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
// @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,7 +12,6 @@ const createRoom = sync.require("./d2m/actions/create-room")
|
||||||
const registerUser = sync.require("./d2m/actions/register-user")
|
const registerUser = sync.require("./d2m/actions/register-user")
|
||||||
const mreq = sync.require("./matrix/mreq")
|
const mreq = sync.require("./matrix/mreq")
|
||||||
const api = sync.require("./matrix/api")
|
const api = sync.require("./matrix/api")
|
||||||
const file = sync.require("./matrix/file")
|
|
||||||
const sendEvent = sync.require("./m2d/actions/send-event")
|
const sendEvent = sync.require("./m2d/actions/send-event")
|
||||||
const eventDispatcher = sync.require("./d2m/event-dispatcher")
|
const eventDispatcher = sync.require("./d2m/event-dispatcher")
|
||||||
const ks = sync.require("./matrix/kstate")
|
const ks = sync.require("./matrix/kstate")
|
||||||
|
|
40
test/data.js
40
test/data.js
|
@ -98,46 +98,6 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
member: {
|
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: {
|
sheep: {
|
||||||
avatar: "38dd359aa12bcd52dd3164126c587f8c",
|
avatar: "38dd359aa12bcd52dd3164126c587f8c",
|
||||||
communication_disabled_until: null,
|
communication_disabled_until: null,
|
||||||
|
|
|
@ -20,7 +20,6 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
||||||
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")
|
||||||
require("../matrix/txnid.test")
|
|
||||||
require("../d2m/converters/message-to-event.test")
|
require("../d2m/converters/message-to-event.test")
|
||||||
require("../d2m/converters/message-to-event.embeds.test")
|
require("../d2m/converters/message-to-event.embeds.test")
|
||||||
require("../d2m/converters/edit-to-changes.test")
|
require("../d2m/converters/edit-to-changes.test")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue