forked from cadence/out-of-your-element
Why did I make it this way??? #13
12 changed files with 187 additions and 6 deletions
|
|
@ -206,14 +206,16 @@ function _hashProfileContent(content, powerLevel) {
|
||||||
* 3. Calculate the power level the user should get based on their Discord permissions
|
* 3. Calculate the power level the user should get based on their Discord permissions
|
||||||
* 4. Compare against the previously known state content, which is helpfully stored in the database
|
* 4. Compare against the previously known state content, which is helpfully stored in the database
|
||||||
* 5. If the state content or power level have changed, send them to Matrix and update them in the database for next time
|
* 5. If the state content or power level have changed, send them to Matrix and update them in the database for next time
|
||||||
|
* 6. If the sim is for a user-installed app, check which user it was added by
|
||||||
* @param {DiscordTypes.APIUser} user
|
* @param {DiscordTypes.APIUser} user
|
||||||
* @param {Omit<DiscordTypes.APIGuildMember, "user"> | undefined} member
|
* @param {Omit<DiscordTypes.APIGuildMember, "user"> | undefined} member
|
||||||
* @param {DiscordTypes.APIGuildChannel} channel
|
* @param {DiscordTypes.APIGuildChannel} channel
|
||||||
* @param {DiscordTypes.APIGuild} guild
|
* @param {DiscordTypes.APIGuild} guild
|
||||||
* @param {string} roomID
|
* @param {string} roomID
|
||||||
|
* @param {DiscordTypes.APIMessageInteractionMetadata} [interactionMetadata]
|
||||||
* @returns {Promise<string>} mxid of the updated sim
|
* @returns {Promise<string>} mxid of the updated sim
|
||||||
*/
|
*/
|
||||||
async function syncUser(user, member, channel, guild, roomID) {
|
async function syncUser(user, member, channel, guild, roomID, interactionMetadata) {
|
||||||
const mxid = await ensureSimJoined(user, roomID)
|
const mxid = await ensureSimJoined(user, roomID)
|
||||||
const content = await memberToStateContent(user, member, guild.id)
|
const content = await memberToStateContent(user, member, guild.id)
|
||||||
const powerLevel = memberToPowerLevel(user, member, guild, channel)
|
const powerLevel = memberToPowerLevel(user, member, guild, channel)
|
||||||
|
|
@ -222,6 +224,12 @@ async function syncUser(user, member, channel, guild, roomID) {
|
||||||
allowOverwrite: !!member,
|
allowOverwrite: !!member,
|
||||||
globalProfile: await userToGlobalProfile(user)
|
globalProfile: await userToGlobalProfile(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const appInstalledByUser = user.bot && interactionMetadata?.authorizing_integration_owners?.[DiscordTypes.ApplicationIntegrationType.UserInstall]
|
||||||
|
if (appInstalledByUser) {
|
||||||
|
db.prepare("INSERT OR IGNORE INTO app_user_install (app_bot_id, user_id, guild_id) VALUES (?, ?, ?)").run(user.id, appInstalledByUser, guild.id)
|
||||||
|
}
|
||||||
|
|
||||||
return mxid
|
return mxid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
26
src/d2m/actions/remove-member.js
Normal file
26
src/d2m/actions/remove-member.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {sync, db, select, from} = passthrough
|
||||||
|
/** @type {import("../../matrix/api")} */
|
||||||
|
const api = sync.require("../../matrix/api")
|
||||||
|
/** @type {import("../converters/remove-member-mxids")} */
|
||||||
|
const removeMemberMxids = sync.require("../converters/remove-member-mxids")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userID discord user ID that left
|
||||||
|
* @param {string} guildID discord guild ID that they left
|
||||||
|
*/
|
||||||
|
async function removeMember(userID, guildID) {
|
||||||
|
const {userAppDeletions, membership} = removeMemberMxids.removeMemberMxids(userID, guildID)
|
||||||
|
db.transaction(() => {
|
||||||
|
for (const d of userAppDeletions) {
|
||||||
|
db.prepare("DELETE FROM app_user_install WHERE guild_id = ? and user_id = ?").run(guildID, d)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
for (const m of membership) {
|
||||||
|
await api.leaveRoom(m.room_id, m.mxid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.removeMember = removeMember
|
||||||
|
|
@ -51,7 +51,7 @@ async function sendMessage(message, channel, guild, row) {
|
||||||
if (message.author.id === discord.application.id) {
|
if (message.author.id === discord.application.id) {
|
||||||
// no need to sync the bot's own user
|
// no need to sync the bot's own user
|
||||||
} else {
|
} else {
|
||||||
senderMxid = await registerUser.syncUser(message.author, message.member, channel, guild, roomID)
|
senderMxid = await registerUser.syncUser(message.author, message.member, channel, guild, roomID, message.interaction_metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ test("message2event components: pk question mark output", async t => {
|
||||||
+ "<hr>"
|
+ "<hr>"
|
||||||
+ "<blockquote><p><strong>System:</strong> INX (<code>xffgnx</code>)"
|
+ "<blockquote><p><strong>System:</strong> INX (<code>xffgnx</code>)"
|
||||||
+ "<br><strong>Member:</strong> Lillith (<code>pphhoh</code>)"
|
+ "<br><strong>Member:</strong> Lillith (<code>pphhoh</code>)"
|
||||||
+ "<br><strong>Sent by:</strong> infinidoge1337 (@unknown-user:)"
|
+ "<br><strong>Sent by:</strong> infinidoge1337 (<a href=\"https://matrix.to/#/@_ooye_infinidoge1337:cadence.moe\">@unknown-user</a>)"
|
||||||
+ "<br><br><strong>Account Roles (7)</strong>"
|
+ "<br><br><strong>Account Roles (7)</strong>"
|
||||||
+ "<br>§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping</p>"
|
+ "<br>§b, !, ‼, Ears Port Ping, Ears Update Ping, Yttr Ping, unsup Ping</p>"
|
||||||
+ `🖼️ <a href="https://files.inx.moe/p/cdn/lillith.webp">https://files.inx.moe/p/cdn/lillith.webp</a>`
|
+ `🖼️ <a href="https://files.inx.moe/p/cdn/lillith.webp">https://files.inx.moe/p/cdn/lillith.webp</a>`
|
||||||
|
|
|
||||||
38
src/d2m/converters/remove-member-mxids.js
Normal file
38
src/d2m/converters/remove-member-mxids.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const passthrough = require("../../passthrough")
|
||||||
|
const {db, select, from} = passthrough
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userID discord user ID that left
|
||||||
|
* @param {string} guildID discord guild ID that they left
|
||||||
|
*/
|
||||||
|
function removeMemberMxids(userID, guildID) {
|
||||||
|
// Get sims for user and remove
|
||||||
|
let membership = from("sim").join("sim_member", "mxid").join("channel_room", "room_id")
|
||||||
|
.select("room_id", "mxid").where({user_id: userID, guild_id: guildID}).and("ORDER BY room_id, mxid").all()
|
||||||
|
membership = membership.concat(from("sim_proxy").join("sim", "user_id").join("sim_member", "mxid").join("channel_room", "room_id")
|
||||||
|
.select("room_id", "mxid").where({proxy_owner_id: userID, guild_id: guildID}).and("ORDER BY room_id, mxid").all())
|
||||||
|
|
||||||
|
// Get user installed apps and remove
|
||||||
|
/** @type {string[]} */
|
||||||
|
let userAppDeletions = []
|
||||||
|
// 1. Select apps that have 1 user remaining
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
const appsWithOneUser = new Set(db.prepare("SELECT app_bot_id FROM app_user_install WHERE guild_id = ? GROUP BY app_bot_id HAVING count(*) = 1").pluck().all(guildID))
|
||||||
|
// 2. Select apps installed by this user
|
||||||
|
const appsFromThisUser = new Set(select("app_user_install", "app_bot_id", {guild_id: guildID, user_id: userID}).pluck().all())
|
||||||
|
if (appsFromThisUser.size) userAppDeletions.push(userID)
|
||||||
|
// Then remove user installed apps if this was the last user with them
|
||||||
|
const appsToRemove = appsWithOneUser.intersection(appsFromThisUser)
|
||||||
|
for (const botID of appsToRemove) {
|
||||||
|
// Remove sims for user installed app
|
||||||
|
const appRemoval = removeMemberMxids(botID, guildID)
|
||||||
|
membership = membership.concat(appRemoval.membership)
|
||||||
|
userAppDeletions = userAppDeletions.concat(appRemoval.userAppDeletions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {membership, userAppDeletions}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.removeMemberMxids = removeMemberMxids
|
||||||
43
src/d2m/converters/remove-member-mxids.test.js
Normal file
43
src/d2m/converters/remove-member-mxids.test.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const {test} = require("supertape")
|
||||||
|
const {removeMemberMxids} = require("./remove-member-mxids")
|
||||||
|
|
||||||
|
test("remove member mxids: would remove mxid for all rooms in this server", t => {
|
||||||
|
t.deepEqual(removeMemberMxids("772659086046658620", "112760669178241024"), {
|
||||||
|
userAppDeletions: [],
|
||||||
|
membership: [{
|
||||||
|
mxid: "@_ooye_cadence:cadence.moe",
|
||||||
|
room_id: "!fGgIymcYWOqjbSRUdV:cadence.moe"
|
||||||
|
}, {
|
||||||
|
mxid: "@_ooye_cadence:cadence.moe",
|
||||||
|
room_id: "!kLRqKKUQXcibIMtOpl:cadence.moe"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove member mxids: removes sims too", t => {
|
||||||
|
t.deepEqual(removeMemberMxids("196188877885538304", "112760669178241024"), {
|
||||||
|
userAppDeletions: [],
|
||||||
|
membership: [{
|
||||||
|
mxid: '@_ooye_ampflower:cadence.moe',
|
||||||
|
room_id: '!qzDBLKlildpzrrOnFZ:cadence.moe'
|
||||||
|
}, {
|
||||||
|
mxid: '@_ooye__pk_zoego:cadence.moe',
|
||||||
|
room_id: '!qzDBLKlildpzrrOnFZ:cadence.moe'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("remove member mxids: removes apps too", t => {
|
||||||
|
t.deepEqual(removeMemberMxids("197126718400626689", "66192955777486848"), {
|
||||||
|
userAppDeletions: ["197126718400626689"],
|
||||||
|
membership: [{
|
||||||
|
mxid: '@_ooye_infinidoge1337:cadence.moe',
|
||||||
|
room_id: '!BnKuBPCvyfOkhcUjEu:cadence.moe'
|
||||||
|
}, {
|
||||||
|
mxid: '@_ooye_evil_lillith_sheher:cadence.moe',
|
||||||
|
room_id: '!BnKuBPCvyfOkhcUjEu:cadence.moe'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -49,8 +49,9 @@ const utils = {
|
||||||
if (listen === "full") {
|
if (listen === "full") {
|
||||||
try {
|
try {
|
||||||
await eventDispatcher.checkMissedExpressions(message.d)
|
await eventDispatcher.checkMissedExpressions(message.d)
|
||||||
await eventDispatcher.checkMissedPins(client, message.d)
|
|
||||||
await eventDispatcher.checkMissedMessages(client, message.d)
|
await eventDispatcher.checkMissedMessages(client, message.d)
|
||||||
|
await eventDispatcher.checkMissedPins(client, message.d)
|
||||||
|
await eventDispatcher.checkMissedLeaves(client, 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)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ const speedbump = sync.require("./actions/speedbump")
|
||||||
const retrigger = sync.require("./actions/retrigger")
|
const retrigger = sync.require("./actions/retrigger")
|
||||||
/** @type {import("./actions/set-presence")} */
|
/** @type {import("./actions/set-presence")} */
|
||||||
const setPresence = sync.require("./actions/set-presence")
|
const setPresence = sync.require("./actions/set-presence")
|
||||||
|
/** @type {import("./actions/remove-member")} */
|
||||||
|
const removeMember = sync.require("./actions/remove-member")
|
||||||
/** @type {import("./actions/poll-vote")} */
|
/** @type {import("./actions/poll-vote")} */
|
||||||
const vote = sync.require("./actions/poll-vote")
|
const vote = sync.require("./actions/poll-vote")
|
||||||
/** @type {import("../m2d/event-dispatcher")} */
|
/** @type {import("../m2d/event-dispatcher")} */
|
||||||
|
|
@ -172,6 +174,31 @@ module.exports = {
|
||||||
await createSpace.syncSpaceExpressions(data, true)
|
await createSpace.syncSpaceExpressions(data, true)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When logging back in, check if any members left while we were gone.
|
||||||
|
* Do this by getting the member list from Discord and seeing who we still have locally that isn't there in the response.
|
||||||
|
* @param {import("./discord-client")} client
|
||||||
|
* @param {DiscordTypes.GatewayGuildCreateDispatchData} guild
|
||||||
|
*/
|
||||||
|
async checkMissedLeaves(client, guild) {
|
||||||
|
const maxLimit = 1000
|
||||||
|
if (guild.member_count >= maxLimit) return // too large to want to scan
|
||||||
|
const discordMembers = await client.snow.guild.getGuildMembers(guild.id, {limit: maxLimit})
|
||||||
|
if (discordMembers.length >= maxLimit) return // response was maxed out, there are guild members that weren't listed, can't act safely
|
||||||
|
const discordMembersSet = new Set(discordMembers.map(m => m.user.id))
|
||||||
|
// no indexes on this one but I'll cope
|
||||||
|
const membersAddedOnMatrix = new Set(from("sim").join("sim_member", "mxid").join("channel_room", "room_id")
|
||||||
|
.pluck("user_id").selectUnsafe("DISTINCT user_id").where({guild_id: guild.id}).and("AND user_id not like '%-%' and user_id not like '%\\_%' escape '\\'").all())
|
||||||
|
const userInstalledAppIDs = new Set(from("app_user_install").pluck("app_bot_id").selectUnsafe("DISTINCT app_bot_id").where({guild_id: guild.id}).all())
|
||||||
|
// loop over members added on matrix and if the member does not exist on discord-side then they should be removed
|
||||||
|
for (const userID of membersAddedOnMatrix) {
|
||||||
|
if (userInstalledAppIDs.has(userID)) continue // skip user installed apps here since they're never true members - they'll be removed by removeMember when the associated user is removed
|
||||||
|
if (!discordMembersSet.has(userID)) {
|
||||||
|
await removeMember.removeMember(userID, guild.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Announces to the parent room that the thread room has been created.
|
* Announces to the parent room that the thread room has been created.
|
||||||
* See notes.md, "Ignore MESSAGE_UPDATE and bridge THREAD_CREATE as the announcement"
|
* See notes.md, "Ignore MESSAGE_UPDATE and bridge THREAD_CREATE as the announcement"
|
||||||
|
|
@ -211,6 +238,14 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("./discord-client")} client
|
||||||
|
* @param {DiscordTypes.GatewayGuildMemberRemoveDispatchData} data
|
||||||
|
*/
|
||||||
|
async GUILD_MEMBER_REMOVE(client, data) {
|
||||||
|
await removeMember.removeMember(data.user.id, data.guild_id)
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("./discord-client")} client
|
* @param {import("./discord-client")} client
|
||||||
* @param {DiscordTypes.GatewayChannelUpdateDispatchData} channelOrThread
|
* @param {DiscordTypes.GatewayChannelUpdateDispatchData} channelOrThread
|
||||||
|
|
|
||||||
10
src/db/migrations/0036-app-user-install.sql
Normal file
10
src/db/migrations/0036-app-user-install.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE "app_user_install" (
|
||||||
|
"guild_id" TEXT NOT NULL,
|
||||||
|
"app_bot_id" TEXT NOT NULL,
|
||||||
|
"user_id" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY ("guild_id", "app_bot_id", "user_id")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
6
src/db/orm-defs.d.ts
vendored
6
src/db/orm-defs.d.ts
vendored
|
|
@ -1,4 +1,10 @@
|
||||||
export type Models = {
|
export type Models = {
|
||||||
|
app_user_install: {
|
||||||
|
guild_id: string
|
||||||
|
app_bot_id: string
|
||||||
|
user_id: string
|
||||||
|
}
|
||||||
|
|
||||||
auto_emoji: {
|
auto_emoji: {
|
||||||
name: string
|
name: string
|
||||||
emoji_id: string
|
emoji_id: string
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,28 @@ INSERT INTO sim (user_id, username, sim_name, mxid) VALUES
|
||||||
('1109360903096369153', 'Amanda', 'amanda', '@_ooye_amanda:cadence.moe'),
|
('1109360903096369153', 'Amanda', 'amanda', '@_ooye_amanda:cadence.moe'),
|
||||||
('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_pk_zoego', '@_ooye__pk_zoego:cadence.moe'),
|
('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '_pk_zoego', '_pk_zoego', '@_ooye__pk_zoego:cadence.moe'),
|
||||||
('320067006521147393', 'papiophidian', 'papiophidian', '@_ooye_papiophidian:cadence.moe'),
|
('320067006521147393', 'papiophidian', 'papiophidian', '@_ooye_papiophidian:cadence.moe'),
|
||||||
('772659086046658620', 'cadence.worm', 'cadence', '@_ooye_cadence:cadence.moe');
|
('772659086046658620', 'cadence.worm', 'cadence', '@_ooye_cadence:cadence.moe'),
|
||||||
|
('196188877885538304', 'ampflower', 'ampflower', '@_ooye_ampflower:cadence.moe'),
|
||||||
|
('1458668878107381800', 'Evil Lillith (she/her)', 'evil_lillith_sheher', '@_ooye_evil_lillith_sheher:cadence.moe'),
|
||||||
|
('197126718400626689', 'infinidoge1337', 'infinidoge1337', '@_ooye_infinidoge1337:cadence.moe');
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO sim_member (mxid, room_id, hashed_profile_content) VALUES
|
INSERT INTO sim_member (mxid, room_id, hashed_profile_content) VALUES
|
||||||
('@_ooye_bojack_horseman:cadence.moe', '!hYnGGlPHlbujVVfktC:cadence.moe', NULL),
|
('@_ooye_bojack_horseman:cadence.moe', '!hYnGGlPHlbujVVfktC:cadence.moe', NULL),
|
||||||
('@_ooye_cadence:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL);
|
('@_ooye_cadence:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL),
|
||||||
|
('@_ooye_cadence:cadence.moe', '!kLRqKKUQXcibIMtOpl:cadence.moe', NULL),
|
||||||
|
('@_ooye_cadence:cadence.moe', '!fGgIymcYWOqjbSRUdV:cadence.moe', NULL),
|
||||||
|
('@_ooye_ampflower:cadence.moe', '!qzDBLKlildpzrrOnFZ:cadence.moe', NULL),
|
||||||
|
('@_ooye__pk_zoego:cadence.moe', '!qzDBLKlildpzrrOnFZ:cadence.moe', NULL),
|
||||||
|
('@_ooye_infinidoge1337:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL),
|
||||||
|
('@_ooye_evil_lillith_sheher:cadence.moe', '!BnKuBPCvyfOkhcUjEu:cadence.moe', NULL);
|
||||||
|
|
||||||
INSERT INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES
|
INSERT INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES
|
||||||
('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304', 'Azalea &flwr; 🌺');
|
('43d378d5-1183-47dc-ab3c-d14e21c3fe58', '196188877885538304', 'Azalea &flwr; 🌺');
|
||||||
|
|
||||||
|
INSERT INTO app_user_install (guild_id, app_bot_id, user_id) VALUES
|
||||||
|
('66192955777486848', '1458668878107381800', '197126718400626689');
|
||||||
|
|
||||||
INSERT INTO message_room (message_id, historical_room_index)
|
INSERT INTO message_room (message_id, historical_room_index)
|
||||||
WITH a (message_id, channel_id) AS (VALUES
|
WITH a (message_id, channel_id) AS (VALUES
|
||||||
('1106366167788044450', '122155380120748034'),
|
('1106366167788044450', '122155380120748034'),
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ file._actuallyUploadDiscordFileToMxc = function(url, res) { throw new Error(`Not
|
||||||
require("../src/d2m/converters/message-to-event.test.embeds")
|
require("../src/d2m/converters/message-to-event.test.embeds")
|
||||||
require("../src/d2m/converters/message-to-event.test.pk")
|
require("../src/d2m/converters/message-to-event.test.pk")
|
||||||
require("../src/d2m/converters/pins-to-list.test")
|
require("../src/d2m/converters/pins-to-list.test")
|
||||||
|
require("../src/d2m/converters/remove-member-mxids.test")
|
||||||
require("../src/d2m/converters/remove-reaction.test")
|
require("../src/d2m/converters/remove-reaction.test")
|
||||||
require("../src/d2m/converters/thread-to-announcement.test")
|
require("../src/d2m/converters/thread-to-announcement.test")
|
||||||
require("../src/d2m/converters/user-to-mxid.test")
|
require("../src/d2m/converters/user-to-mxid.test")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue