// @ts-check const assert = require("assert").strict /** @type {any} */ // @ts-ignore bad types from semaphore const Semaphore = require("@chriscdn/promise-semaphore") const sqlite = require("better-sqlite3") const HeatSync = require("heatsync") const config = require("../config") const passthrough = require("../passthrough") const sync = new HeatSync({watchFS: false}) /** @type {import("../matrix/read-registration")} */ const reg = sync.require("../matrix/read-registration") assert(reg.old_bridge) const oldAT = reg.old_bridge.as_token const newAT = reg.as_token const oldDB = new sqlite(reg.old_bridge.database) const db = new sqlite("db/ooye.db") db.exec(`CREATE TABLE IF NOT EXISTS migration ( discord_channel TEXT NOT NULL, migrated INTEGER NOT NULL, PRIMARY KEY("discord_channel") ) WITHOUT ROWID;`) Object.assign(passthrough, {config, sync, db}) const DiscordClient = require("../d2m/discord-client") const discord = new DiscordClient(config.discordToken, "half") passthrough.discord = discord /** @type {import("../d2m/actions/create-space")} */ const createSpace = sync.require("../d2m/actions/create-space") /** @type {import("../d2m/actions/create-room")} */ const createRoom = sync.require("../d2m/actions/create-room") /** @type {import("../matrix/mreq")} */ const mreq = sync.require("../matrix/mreq") /** @type {import("../matrix/api")} */ const api = sync.require("../matrix/api") const sema = new Semaphore() ;(async () => { await discord.cloud.connect() console.log("Discord gateway started") discord.cloud.on("event", event => onPacket(discord, event)) })() /** @param {DiscordClient} discord */ function onPacket(discord, event) { if (event.t === "GUILD_CREATE") { const guild = event.d if (["1100319549670301727", "112760669178241024", "497159726455455754"].includes(guild.id)) return sema.request(() => migrateGuild(guild)) } } const newBridgeMxid = `@${reg.sender_localpart}:${reg.ooye.server_name}` /** @param {import("discord-api-types/v10").GatewayGuildCreateDispatchData} guild */ async function migrateGuild(guild) { console.log(`START MIGRATION of ${guild.name} (${guild.id})`) // Step 1: Create a new space for the guild (createSpace) const spaceID = await createSpace.syncSpace(guild.id) let oldRooms = oldDB.prepare("SELECT matrix_id, discord_guild, discord_channel FROM room_entries INNER JOIN remote_room_data ON remote_id = room_id WHERE discord_guild = ?").all(guild.id) const migrated = db.prepare("SELECT discord_channel FROM migration WHERE migrated = 1").pluck().all() oldRooms = oldRooms.filter(row => discord.channels.has(row.discord_channel) && !migrated.includes(row.discord_channel)) console.log("Found these rooms which can be migrated:") console.log(oldRooms) for (const row of oldRooms) { const roomID = row.matrix_id const channel = discord.channels.get(row.discord_channel) assert(channel) // Step 2: (Using old bridge access token) Join the new bridge to the old rooms and give it PL 100 console.log(`-- Joining channel ${channel.name}...`) await mreq.withAccessToken(oldAT, async () => { try { await api.inviteToRoom(roomID, newBridgeMxid) } catch (e) { if (e.message.includes("is already in the room")) { // Great! } else { throw e } } await api.setUserPower(roomID, newBridgeMxid, 100) }) await api.joinRoom(roomID) // Step 3: Remove the old bridge's aliases console.log(`-- -- Deleting aliases...`) await mreq.withAccessToken(oldAT, async () => { // have to run as old application service since the AS owns its aliases const aliases = (await mreq.mreq("GET", `/client/v3/rooms/${roomID}/aliases`)).aliases for (const alias of aliases) { if (alias.match(/^#?_?discord/)) { await mreq.mreq("DELETE", `/client/v3/directory/room/${alias.replace(/#/g, "%23")}`) } } await api.sendState(roomID, "m.room.canonical_alias", "", {}) }) // Step 4: Add old rooms to new database; they are now also the new rooms db.prepare("REPLACE INTO channel_room (channel_id, room_id, name) VALUES (?, ?, ?)").run(channel.id, row.matrix_id, channel.name) console.log(`-- -- Added to database`) // Step 5: Call syncRoom for each room await createRoom.syncRoom(row.discord_channel) console.log(`-- -- Finished syncing`) db.prepare("INSERT INTO migration (discord_channel, migrated) VALUES (?, 1)").run(channel.id) } // Step 5: Call syncSpace to make sure everything is up to date await createSpace.syncSpace(guild.id) console.log(`Finished migrating ${guild.name} to Out Of Your Element`) }