diff --git a/scripts/capture-message-update-events.js b/scripts/capture-message-update-events.js
index 2ff9b49..f345d3f 100644
--- a/scripts/capture-message-update-events.js
+++ b/scripts/capture-message-update-events.js
@@ -23,9 +23,9 @@ const sync = new HeatSync({watchFS: false})
Object.assign(passthrough, {config, sync})
-const DiscordClient = require("../d2m/discord-client", false)
+const DiscordClient = require("../d2m/discord-client")
-const discord = new DiscordClient(config.discordToken, false)
+const discord = new DiscordClient(config.discordToken, "no")
passthrough.discord = discord
;(async () => {
diff --git a/scripts/migrate-from-old-bridge.js b/scripts/migrate-from-old-bridge.js
new file mode 100644
index 0000000..4504aea
--- /dev/null
+++ b/scripts/migrate-from-old-bridge.js
@@ -0,0 +1,124 @@
+// @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`)
+}
diff --git a/scripts/save-channel-names-to-db.js b/scripts/save-channel-names-to-db.js
index 6f5867a..1e5c541 100644
--- a/scripts/save-channel-names-to-db.js
+++ b/scripts/save-channel-names-to-db.js
@@ -13,7 +13,7 @@ Object.assign(passthrough, {config, sync, db})
const DiscordClient = require("../d2m/discord-client")
-const discord = new DiscordClient(config.discordToken, false)
+const discord = new DiscordClient(config.discordToken, "no")
passthrough.discord = discord
;(async () => {
|