forked from cadence/out-of-your-element
Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 116aa1aa2f | |||
| fa9cadadcc | |||
| 3f91399a9e | |||
| 83e67fbdc0 | |||
| 6553946b88 | |||
| ebc4dc1814 |
9 changed files with 415 additions and 9 deletions
|
|
@ -38,12 +38,8 @@ passthrough.select = orm.select
|
||||||
|
|
||||||
/** @type {import("../src/d2m/event-dispatcher")}*/
|
/** @type {import("../src/d2m/event-dispatcher")}*/
|
||||||
const eventDispatcher = sync.require("../src/d2m/event-dispatcher")
|
const eventDispatcher = sync.require("../src/d2m/event-dispatcher")
|
||||||
|
/** @type {import("../src/d2m/actions/create-room")} */
|
||||||
const roomID = passthrough.select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
|
const createRoom = sync.require("../src/d2m/actions/create-room")
|
||||||
if (!roomID) {
|
|
||||||
console.error("Please choose a channel that's already bridged.")
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await discord.cloud.connect()
|
await discord.cloud.connect()
|
||||||
|
|
@ -60,6 +56,18 @@ async function event(event) {
|
||||||
if (!channel) return
|
if (!channel) return
|
||||||
const guild_id = event.d.id
|
const guild_id = event.d.id
|
||||||
|
|
||||||
|
let roomID = passthrough.select("channel_room", "room_id", {channel_id: channelID}).pluck().get()
|
||||||
|
if (!roomID) {
|
||||||
|
console.log(`Channel #${channel.name} is not bridged yet. Attempting to auto-create...`)
|
||||||
|
try {
|
||||||
|
roomID = await createRoom.syncRoom(channelID)
|
||||||
|
console.log(`Successfully bridged to new room: ${roomID}`)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to auto-create room: ${e.message}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let last = backfill.prepare("SELECT cast(max(message_id) as TEXT) FROM backfill WHERE channel_id = ?").pluck().get(channelID) || "0"
|
let last = backfill.prepare("SELECT cast(max(message_id) as TEXT) FROM backfill WHERE channel_id = ?").pluck().get(channelID) || "0"
|
||||||
console.log(`OK, processing messages for #${channel.name}, continuing from ${last}`)
|
console.log(`OK, processing messages for #${channel.name}, continuing from ${last}`)
|
||||||
|
|
||||||
|
|
|
||||||
124
src/d2m/actions/remove-member.js
Normal file
124
src/d2m/actions/remove-member.js
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
|
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {discord, sync, db, select, from} = passthrough
|
||||||
|
/** @type {import("../../matrix/api")} */
|
||||||
|
const api = sync.require("../../matrix/api")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a specific sim leave all rooms in a guild and clean up the database.
|
||||||
|
* @param {string} mxid
|
||||||
|
* @param {string} guildID
|
||||||
|
* @returns {Promise<number>} number of rooms left
|
||||||
|
*/
|
||||||
|
async function removeSimFromGuild(mxid, guildID) {
|
||||||
|
const rooms = from("sim_member")
|
||||||
|
.join("channel_room", "room_id")
|
||||||
|
.where({mxid, guild_id: guildID})
|
||||||
|
.pluck("room_id")
|
||||||
|
.all()
|
||||||
|
|
||||||
|
for (const roomID of rooms) {
|
||||||
|
try {
|
||||||
|
await api.leaveRoom(roomID, mxid)
|
||||||
|
} catch (e) {
|
||||||
|
// Room may no longer exist or sim may already have left
|
||||||
|
console.log(`[remove member] failed to leave room ${roomID}: ${e}`)
|
||||||
|
}
|
||||||
|
db.prepare("DELETE FROM sim_member WHERE mxid = ? AND room_id = ?").run(mxid, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rooms.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a user's sim and their PluralKit proxy sims from all rooms in a guild.
|
||||||
|
* Called when a Discord user leaves a guild.
|
||||||
|
* @param {string} userID Discord user ID
|
||||||
|
* @param {string} guildID Discord guild ID
|
||||||
|
*/
|
||||||
|
async function removeMember(userID, guildID) {
|
||||||
|
// Remove the user's own sim
|
||||||
|
const mxid = select("sim", "mxid", {user_id: userID}).pluck().get()
|
||||||
|
if (mxid) {
|
||||||
|
const count = await removeSimFromGuild(mxid, guildID)
|
||||||
|
if (count) console.log(`[remove member] removed sim for ${userID} from ${count} rooms in guild ${guildID}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove PluralKit proxy sims owned by this user
|
||||||
|
const pkUserIDs = select("sim_proxy", "user_id", {proxy_owner_id: userID}).pluck().all()
|
||||||
|
for (const pkUserID of pkUserIDs) {
|
||||||
|
const pkMxid = select("sim", "mxid", {user_id: pkUserID}).pluck().get()
|
||||||
|
if (!pkMxid) continue
|
||||||
|
const count = await removeSimFromGuild(pkMxid, guildID)
|
||||||
|
if (count) console.log(`[remove member] removed pk sim for ${pkUserID} (owner: ${userID}) from ${count} rooms in guild ${guildID}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfill: check all sims in guild rooms and remove those whose Discord users have left the guild.
|
||||||
|
* Called on GUILD_CREATE to catch removals that happened while the bridge was offline.
|
||||||
|
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
|
||||||
|
*/
|
||||||
|
async function checkMissedMembers(guild) {
|
||||||
|
if (guild.unavailable) return
|
||||||
|
|
||||||
|
// Find all distinct regular Discord user IDs with sims in this guild's rooms
|
||||||
|
// Exclude PK sims (UUIDs with dashes) and webhook sims (with underscores)
|
||||||
|
const rows = from("sim_member")
|
||||||
|
.join("channel_room", "room_id")
|
||||||
|
.join("sim", "mxid")
|
||||||
|
.where({guild_id: guild.id})
|
||||||
|
.and("AND user_id NOT LIKE '%-%' AND user_id NOT LIKE '%\\_%' ESCAPE '\\'")
|
||||||
|
.pluck("user_id")
|
||||||
|
.all()
|
||||||
|
|
||||||
|
const userIDs = [...new Set(rows)]
|
||||||
|
|
||||||
|
for (const userID of userIDs) {
|
||||||
|
try {
|
||||||
|
await discord.snow.guild.getGuildMember(guild.id, userID)
|
||||||
|
} catch (e) {
|
||||||
|
if (String(e).includes("10007") || String(e).includes("Unknown Member")) {
|
||||||
|
console.log(`[remove member] backfill: user ${userID} is no longer in guild ${guild.id}`)
|
||||||
|
await removeMember(userID, guild.id)
|
||||||
|
}
|
||||||
|
// Other errors (rate limits, network issues) - skip this user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check PK proxy owners who may not have a regular sim but whose PK sims are in guild rooms
|
||||||
|
const pkRows = from("sim_member")
|
||||||
|
.join("channel_room", "room_id")
|
||||||
|
.join("sim", "mxid")
|
||||||
|
.join("sim_proxy", "user_id")
|
||||||
|
.where({guild_id: guild.id})
|
||||||
|
.pluck("proxy_owner_id")
|
||||||
|
.all()
|
||||||
|
|
||||||
|
const pkOwnerIDs = [...new Set(pkRows)].filter(id => !userIDs.includes(id))
|
||||||
|
|
||||||
|
for (const ownerID of pkOwnerIDs) {
|
||||||
|
try {
|
||||||
|
await discord.snow.guild.getGuildMember(guild.id, ownerID)
|
||||||
|
} catch (e) {
|
||||||
|
if (String(e).includes("10007") || String(e).includes("Unknown Member")) {
|
||||||
|
console.log(`[remove member] backfill: pk owner ${ownerID} is no longer in guild ${guild.id}`)
|
||||||
|
// Only remove PK sims for this owner, not their own sim (they don't have one in this guild)
|
||||||
|
const pkUserIDs = select("sim_proxy", "user_id", {proxy_owner_id: ownerID}).pluck().all()
|
||||||
|
for (const pkUserID of pkUserIDs) {
|
||||||
|
const pkMxid = select("sim", "mxid", {user_id: pkUserID}).pluck().get()
|
||||||
|
if (!pkMxid) continue
|
||||||
|
const count = await removeSimFromGuild(pkMxid, guild.id)
|
||||||
|
if (count) console.log(`[remove member] backfill: removed pk sim for ${pkUserID} (owner: ${ownerID}) from ${count} rooms in guild ${guild.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.removeSimFromGuild = removeSimFromGuild
|
||||||
|
module.exports.removeMember = removeMember
|
||||||
|
module.exports.checkMissedMembers = checkMissedMembers
|
||||||
202
src/d2m/actions/remove-member.test.js
Normal file
202
src/d2m/actions/remove-member.test.js
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const {test} = require("supertape")
|
||||||
|
const {removeSimFromGuild, removeMember, checkMissedMembers} = require("./remove-member")
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {db, sync, discord} = passthrough
|
||||||
|
|
||||||
|
/** @type {import("../../matrix/api")} */
|
||||||
|
const api = sync.require("../../matrix/api")
|
||||||
|
|
||||||
|
function setupTestSim(userID, simName, mxid) {
|
||||||
|
db.prepare("INSERT OR IGNORE INTO sim (user_id, username, sim_name, mxid) VALUES (?, ?, ?, ?)").run(userID, simName, simName, mxid)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupTestSimMember(mxid, roomID) {
|
||||||
|
db.prepare("INSERT OR IGNORE INTO sim_member (mxid, room_id, hashed_profile_content) VALUES (?, ?, NULL)").run(mxid, roomID)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupTestSim(userID) {
|
||||||
|
const mxid = db.prepare("SELECT mxid FROM sim WHERE user_id = ?").pluck().get(userID)
|
||||||
|
if (mxid) {
|
||||||
|
db.prepare("DELETE FROM sim_member WHERE mxid = ?").run(mxid)
|
||||||
|
}
|
||||||
|
db.prepare("DELETE FROM sim WHERE user_id = ?").run(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("remove-member: removeSimFromGuild removes sim from all guild rooms", async t => {
|
||||||
|
const mxid = "@_ooye_testrm1:cadence.moe"
|
||||||
|
const guildID = "112760669178241024"
|
||||||
|
const roomID1 = "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
const roomID2 = "!fGgIymcYWOqjbSRUdV:cadence.moe"
|
||||||
|
|
||||||
|
setupTestSim("999999990", "testrm1", mxid)
|
||||||
|
setupTestSimMember(mxid, roomID1)
|
||||||
|
setupTestSimMember(mxid, roomID2)
|
||||||
|
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const count = await removeSimFromGuild(mxid, guildID)
|
||||||
|
|
||||||
|
t.equal(count, 2)
|
||||||
|
t.equal(leftRooms.length, 2)
|
||||||
|
t.ok(leftRooms.some(r => r.roomID === roomID1))
|
||||||
|
t.ok(leftRooms.some(r => r.roomID === roomID2))
|
||||||
|
|
||||||
|
const remaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid)
|
||||||
|
t.equal(remaining, 0)
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
cleanupTestSim("999999990")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: removeSimFromGuild only affects rooms in the specified guild", async t => {
|
||||||
|
const mxid = "@_ooye_testrm2:cadence.moe"
|
||||||
|
const guildID = "112760669178241024"
|
||||||
|
const guildRoom = "!kLRqKKUQXcibIMtOpl:cadence.moe" // guild 112760669178241024
|
||||||
|
const otherGuildRoom = "!BnKuBPCvyfOkhcUjEu:cadence.moe" // guild 66192955777486848
|
||||||
|
|
||||||
|
setupTestSim("999999991", "testrm2", mxid)
|
||||||
|
setupTestSimMember(mxid, guildRoom)
|
||||||
|
setupTestSimMember(mxid, otherGuildRoom)
|
||||||
|
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const count = await removeSimFromGuild(mxid, guildID)
|
||||||
|
|
||||||
|
t.equal(count, 1)
|
||||||
|
t.equal(leftRooms[0].roomID, guildRoom)
|
||||||
|
|
||||||
|
const otherRemaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ? AND room_id = ?").pluck().get(mxid, otherGuildRoom)
|
||||||
|
t.equal(otherRemaining, 1, "other guild's room should be untouched")
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
cleanupTestSim("999999991")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: removeSimFromGuild handles leaveRoom errors gracefully", async t => {
|
||||||
|
const mxid = "@_ooye_testrm3:cadence.moe"
|
||||||
|
const guildID = "112760669178241024"
|
||||||
|
const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
|
||||||
|
setupTestSim("999999992", "testrm3", mxid)
|
||||||
|
setupTestSimMember(mxid, roomID)
|
||||||
|
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async () => { throw new Error("not in room") }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const count = await removeSimFromGuild(mxid, guildID)
|
||||||
|
t.equal(count, 1)
|
||||||
|
|
||||||
|
const remaining = db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid)
|
||||||
|
t.equal(remaining, 0, "sim_member should be deleted even if leaveRoom fails")
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
cleanupTestSim("999999992")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: removeMember removes user sim and their PK proxy sims", async t => {
|
||||||
|
const userID = "999999993"
|
||||||
|
const mxid = "@_ooye_testrm4:cadence.moe"
|
||||||
|
const pkUserID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||||
|
const pkMxid = "@_ooye__pk_testrmpk:cadence.moe"
|
||||||
|
const guildID = "112760669178241024"
|
||||||
|
const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
|
||||||
|
setupTestSim(userID, "testrm4", mxid)
|
||||||
|
setupTestSimMember(mxid, roomID)
|
||||||
|
setupTestSim(pkUserID, "_pk_testrmpk", pkMxid)
|
||||||
|
setupTestSimMember(pkMxid, roomID)
|
||||||
|
db.prepare("INSERT OR IGNORE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkUserID, userID, "Test PK")
|
||||||
|
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeMember(userID, guildID)
|
||||||
|
|
||||||
|
t.equal(leftRooms.length, 2)
|
||||||
|
t.ok(leftRooms.some(r => r.mxid === mxid), "user sim should leave")
|
||||||
|
t.ok(leftRooms.some(r => r.mxid === pkMxid), "pk sim should leave")
|
||||||
|
|
||||||
|
t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid), 0)
|
||||||
|
t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(pkMxid), 0)
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
db.prepare("DELETE FROM sim_proxy WHERE user_id = ?").run(pkUserID)
|
||||||
|
cleanupTestSim(userID)
|
||||||
|
cleanupTestSim(pkUserID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: removeMember does nothing for unknown user", async t => {
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await removeMember("000000000000000000", "112760669178241024")
|
||||||
|
t.equal(leftRooms.length, 0)
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: checkMissedMembers removes sims for departed users", async t => {
|
||||||
|
const userID = "999999994"
|
||||||
|
const mxid = "@_ooye_testrm5:cadence.moe"
|
||||||
|
const guildID = "112760669178241024"
|
||||||
|
const roomID = "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
|
||||||
|
setupTestSim(userID, "testrm5", mxid)
|
||||||
|
setupTestSimMember(mxid, roomID)
|
||||||
|
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
const originalSnow = discord.snow
|
||||||
|
discord.snow = {
|
||||||
|
guild: {
|
||||||
|
getGuildMember: async (gid, uid) => {
|
||||||
|
if (uid === userID) throw new Error('{"message": "Unknown Member", "code": 10007}')
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await checkMissedMembers({id: guildID})
|
||||||
|
|
||||||
|
t.ok(leftRooms.some(r => r.mxid === mxid), "departed user's sim should be removed")
|
||||||
|
t.equal(db.prepare("SELECT COUNT(*) FROM sim_member WHERE mxid = ?").pluck().get(mxid), 0)
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
discord.snow = originalSnow
|
||||||
|
cleanupTestSim(userID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove-member: checkMissedMembers skips unavailable guilds", async t => {
|
||||||
|
const leftRooms = []
|
||||||
|
const originalLeaveRoom = api.leaveRoom
|
||||||
|
api.leaveRoom = async (roomID, userMxid) => { leftRooms.push({roomID, mxid: userMxid}) }
|
||||||
|
|
||||||
|
try {
|
||||||
|
await checkMissedMembers({id: "112760669178241024", unavailable: true})
|
||||||
|
t.equal(leftRooms.length, 0, "should not process unavailable guilds")
|
||||||
|
} finally {
|
||||||
|
api.leaveRoom = originalLeaveRoom
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -769,7 +769,21 @@ async function messageToEvent(message, guild, options = {}, di) {
|
||||||
// Then scheduled events
|
// Then scheduled events
|
||||||
if (message.content && di?.snow) {
|
if (message.content && di?.snow) {
|
||||||
for (const match of [...message.content.matchAll(/discord\.gg\/([A-Za-z0-9]+)\?event=([0-9]{18,})/g)]) { // snowflake has minimum 18 because the events feature is at least that old
|
for (const match of [...message.content.matchAll(/discord\.gg\/([A-Za-z0-9]+)\?event=([0-9]{18,})/g)]) { // snowflake has minimum 18 because the events feature is at least that old
|
||||||
const invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]})
|
let invite
|
||||||
|
try {
|
||||||
|
invite = await di.snow.invite.getInvite(match[1], {guild_scheduled_event_id: match[2]})
|
||||||
|
} catch (e) {
|
||||||
|
// Skip expired events and invites
|
||||||
|
if (e.code === 10006 || e.httpStatus === 404) {
|
||||||
|
console.warn(`[Backfill] Skipped expired scheduled event: ${match[0]}`)
|
||||||
|
const fallbackBody = `[Expired Scheduled Event: ${match[0]}]`
|
||||||
|
const fallbackHtml = `<blockquote>Expired Scheduled Event: <a href="https://${match[0]}">${match[0]}</a></blockquote>`
|
||||||
|
await addTextEvent(fallbackBody, fallbackHtml, "m.notice")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
const event = invite.guild_scheduled_event
|
const event = invite.guild_scheduled_event
|
||||||
if (!event) continue // the event ID provided was not valid
|
if (!event) continue // the event ID provided was not valid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1538,6 +1538,38 @@ test("message2event: vc invite event renders embed with room link", async t => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("message2event: expired event invite renders fallback notice", async t => {
|
||||||
|
const events = await messageToEvent({content: "https://discord.gg/placeholder?event=1381190945646710824"}, {}, {}, {
|
||||||
|
snow: {
|
||||||
|
invite: {
|
||||||
|
getInvite: async () => {
|
||||||
|
const error = new Error("Unknown Invite")
|
||||||
|
error.code = 10006
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.deepEqual(events, [
|
||||||
|
{
|
||||||
|
$type: "m.room.message",
|
||||||
|
body: "https://discord.gg/placeholder?event=1381190945646710824",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<a href=\"https://discord.gg/placeholder?event=1381190945646710824\">https://discord.gg/placeholder?event=1381190945646710824</a>",
|
||||||
|
"m.mentions": {},
|
||||||
|
msgtype: "m.text",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$type: "m.room.message",
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: "[Expired Scheduled Event: discord.gg/placeholder?event=1381190945646710824]",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
formatted_body: "<blockquote>Expired Scheduled Event: <a href=\"https://discord.gg/placeholder?event=1381190945646710824\">discord.gg/placeholder?event=1381190945646710824</a></blockquote>",
|
||||||
|
"m.mentions": {}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test("message2event: channel links are converted even inside lists (parser post-processer descends into list items)", async t => {
|
test("message2event: channel links are converted even inside lists (parser post-processer descends into list items)", async t => {
|
||||||
let called = 0
|
let called = 0
|
||||||
const events = await messageToEvent({
|
const events = await messageToEvent({
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class DiscordClient {
|
||||||
/** @type {import("cloudstorm").IClientOptions["intents"]} */
|
/** @type {import("cloudstorm").IClientOptions["intents"]} */
|
||||||
const intents = [
|
const intents = [
|
||||||
"DIRECT_MESSAGES", "DIRECT_MESSAGE_REACTIONS", "DIRECT_MESSAGE_TYPING",
|
"DIRECT_MESSAGES", "DIRECT_MESSAGE_REACTIONS", "DIRECT_MESSAGE_TYPING",
|
||||||
"GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "GUILD_MESSAGE_POLLS",
|
"GUILDS", "GUILD_EMOJIS_AND_STICKERS", "GUILD_MEMBERS", "GUILD_MESSAGES", "GUILD_MESSAGE_REACTIONS", "GUILD_MESSAGE_TYPING", "GUILD_WEBHOOKS", "GUILD_MESSAGE_POLLS",
|
||||||
"MESSAGE_CONTENT"
|
"MESSAGE_CONTENT"
|
||||||
]
|
]
|
||||||
if (reg.ooye.receive_presences !== false) intents.push("GUILD_PRESENCES")
|
if (reg.ooye.receive_presences !== false) intents.push("GUILD_PRESENCES")
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ const utils = {
|
||||||
await eventDispatcher.checkMissedExpressions(message.d)
|
await eventDispatcher.checkMissedExpressions(message.d)
|
||||||
await eventDispatcher.checkMissedPins(client, message.d)
|
await eventDispatcher.checkMissedPins(client, message.d)
|
||||||
await eventDispatcher.checkMissedMessages(client, message.d)
|
await eventDispatcher.checkMissedMessages(client, message.d)
|
||||||
|
await eventDispatcher.checkMissedMembers(message.d)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to sync missed events. To retry, please fix this error and restart OOYE:")
|
console.error("Failed to sync missed events. To retry, please fix this error and restart OOYE:")
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ const vote = sync.require("./actions/poll-vote")
|
||||||
const matrixEventDispatcher = sync.require("../m2d/event-dispatcher")
|
const matrixEventDispatcher = sync.require("../m2d/event-dispatcher")
|
||||||
/** @type {import("../discord/interactions/matrix-info")} */
|
/** @type {import("../discord/interactions/matrix-info")} */
|
||||||
const matrixInfoInteraction = sync.require("../discord/interactions/matrix-info")
|
const matrixInfoInteraction = sync.require("../discord/interactions/matrix-info")
|
||||||
|
/** @type {import("./actions/remove-member")} */
|
||||||
|
const removeMember = sync.require("./actions/remove-member")
|
||||||
|
|
||||||
const {Semaphore} = require("@chriscdn/promise-semaphore")
|
const {Semaphore} = require("@chriscdn/promise-semaphore")
|
||||||
const checkMissedPinsSema = new Semaphore()
|
const checkMissedPinsSema = new Semaphore()
|
||||||
|
|
@ -413,5 +415,24 @@ module.exports = {
|
||||||
const status = data.status
|
const status = data.status
|
||||||
if (!status) return
|
if (!status) return
|
||||||
setPresence.presenceTracker.incomingPresence(data.user.id, data.guild_id, status)
|
setPresence.presenceTracker.incomingPresence(data.user.id, data.guild_id, status)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a Discord user leaves (or is kicked/banned from) the guild, make their sims leave the bridged Matrix rooms.
|
||||||
|
* Also removes PluralKit proxy sims owned by the departing user.
|
||||||
|
* @param {import("./discord-client")} client
|
||||||
|
* @param {DiscordTypes.GatewayGuildMemberRemoveDispatchData} data
|
||||||
|
*/
|
||||||
|
async GUILD_MEMBER_REMOVE(client, data) {
|
||||||
|
if (data.user.id === client.user.id) return // Don't process if the bot itself is removed
|
||||||
|
await removeMember.removeMember(data.user.id, data.guild_id)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When logging back in, check for sims whose Discord users have left the guild while the bridge was offline.
|
||||||
|
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
|
||||||
|
*/
|
||||||
|
async checkMissedMembers(guild) {
|
||||||
|
await removeMember.checkMissedMembers(guild)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ const {green} = require("ansi-colors")
|
||||||
const passthrough = require("../src/passthrough")
|
const passthrough = require("../src/passthrough")
|
||||||
const db = new sqlite(":memory:")
|
const db = new sqlite(":memory:")
|
||||||
|
|
||||||
const {reg} = require("../src/matrix/read-registration")
|
const readReg = require("../src/matrix/read-registration")
|
||||||
|
readReg.reg = readReg.getTemplateRegistration("cadence.moe")
|
||||||
|
const {reg} = readReg
|
||||||
|
reg.url = "http://localhost:6693"
|
||||||
reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby"
|
reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby"
|
||||||
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
|
reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
|
||||||
reg.ooye.server_name = "cadence.moe"
|
reg.ooye.server_name = "cadence.moe"
|
||||||
|
|
@ -145,6 +148,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
||||||
require("../src/d2m/actions/create-room.test")
|
require("../src/d2m/actions/create-room.test")
|
||||||
require("../src/d2m/actions/create-space.test")
|
require("../src/d2m/actions/create-space.test")
|
||||||
require("../src/d2m/actions/register-user.test")
|
require("../src/d2m/actions/register-user.test")
|
||||||
|
require("../src/d2m/actions/remove-member.test")
|
||||||
require("../src/d2m/converters/edit-to-changes.test")
|
require("../src/d2m/converters/edit-to-changes.test")
|
||||||
require("../src/d2m/converters/emoji-to-key.test")
|
require("../src/d2m/converters/emoji-to-key.test")
|
||||||
require("../src/d2m/converters/find-mentions.test")
|
require("../src/d2m/converters/find-mentions.test")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue