From a8e1f95897135542433454ba78ac5dea1ee6ec7e Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 10 May 2023 22:15:20 +1200 Subject: [PATCH] completed user syncing. it occurs on message send --- d2m/actions/create-room.js | 2 + d2m/actions/register-user.js | 76 +++++++++++++++++++++++++++++++++-- d2m/actions/send-message.js | 5 +++ db/ooye.db | Bin 94208 -> 94208 bytes matrix/file.js | 11 +++++ 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/d2m/actions/create-room.js b/d2m/actions/create-room.js index 7f2c799..96a9671 100644 --- a/d2m/actions/create-room.js +++ b/d2m/actions/create-room.js @@ -144,6 +144,8 @@ async function _syncRoom(channelID, shouldActuallySync) { return existing // only need to ensure room exists, and it does. return the room ID } + console.log(`[room sync] to matrix: ${channel.name}`) + const {spaceID, channelKState} = await channelToKState(channel, guild) // sync channel state to room diff --git a/d2m/actions/register-user.js b/d2m/actions/register-user.js index 89bac2c..cc5f515 100644 --- a/d2m/actions/register-user.js +++ b/d2m/actions/register-user.js @@ -80,13 +80,81 @@ async function ensureSimJoined(user, roomID) { /** * @param {import("discord-api-types/v10").APIUser} user - * @param {Required>} member + * @param {Omit} member */ -async function memberToStateContent(user, member) { - return { - displayname: member.nick || user.username +async function memberToStateContent(user, member, guildID) { + let displayname = user.username + if (member.nick && member.nick !== displayname) displayname = member.nick + " | " + displayname // prepend nick if present + + const content = { + displayname, + membership: "join", + "moe.cadence.ooye.member": { + }, + "uk.half-shot.discord.member": { + bot: !!user.bot, + displayColor: user.accent_color, + id: user.id, + username: user.discriminator.length === 4 ? `${user.username}#${user.discriminator}` : `@${user.username}` + } + } + + if (member.avatar || user.avatar) { + // const avatarPath = file.userAvatar(user) // the user avatar only + const avatarPath = file.memberAvatar(guildID, user, member) // the member avatar or the user avatar + content["moe.cadence.ooye.member"].avatar = avatarPath + content.avatar_url = await file.uploadDiscordFileToMxc(avatarPath) + } + + return content +} + +function calculateProfileEventContentHash(content) { + return `${content.displayname}\u0000${content.avatar_url}` +} + +/** + * @param {import("discord-api-types/v10").APIUser} user + * @param {Omit} member + */ +async function syncUser(user, member, guildID, roomID) { + const mxid = await ensureSimJoined(user, roomID) + const content = await memberToStateContent(user, member, guildID) + const profileEventContentHash = calculateProfileEventContentHash(content) + const existingHash = db.prepare("SELECT profile_event_content_hash FROM sim_member WHERE room_id = ? AND mxid = ?").pluck().get(roomID, mxid) + // only do the actual sync if the hash has changed since we last looked + if (existingHash !== profileEventContentHash) { + await api.sendState(roomID, "m.room.member", mxid, content, mxid) + db.prepare("UPDATE sim_member SET profile_event_content_hash = ? WHERE room_id = ? AND mxid = ?").run(profileEventContentHash, roomID, mxid) + } +} + +async function syncAllUsersInRoom(roomID) { + const mxids = db.prepare("SELECT mxid FROM sim_member WHERE room_id = ?").pluck().all(roomID) + + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(roomID) + assert.ok(typeof channelID === "string") + /** @ts-ignore @type {import("discord-api-types/v10").APIGuildChannel} */ + const channel = discord.channels.get(channelID) + const guildID = channel.guild_id + assert.ok(typeof guildID === "string") + + for (const mxid of mxids) { + const userID = db.prepare("SELECT discord_id FROM sim WHERE mxid = ?").pluck().get(mxid) + assert.ok(typeof userID === "string") + + /** @ts-ignore @type {Required} */ + const member = await discord.snow.guild.getGuildMember(guildID, userID) + /** @ts-ignore @type {Required} user */ + const user = member.user + assert.ok(user) + + console.log(`[user sync] to matrix: ${user.username} in ${channel.name}`) + await syncUser(user, member, guildID, roomID) } } module.exports.ensureSim = ensureSim module.exports.ensureSimJoined = ensureSimJoined +module.exports.syncUser = syncUser +module.exports.syncAllUsersInRoom = syncAllUsersInRoom diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index fb181d2..2630430 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -1,5 +1,7 @@ // @ts-check +const assert = require("assert") + const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("../converters/message-to-event")} */ @@ -15,11 +17,14 @@ const createRoom = sync.require("../actions/create-room") * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message */ async function sendMessage(message) { + assert.ok(message.member) + const event = messageToEvent.messageToEvent(message) const roomID = await createRoom.ensureRoom(message.channel_id) let senderMxid = null if (!message.webhook_id) { senderMxid = await registerUser.ensureSimJoined(message.author, roomID) + await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } const eventID = await api.sendEvent(roomID, "m.room.message", event, senderMxid) db.prepare("INSERT INTO event_message (event_id, message_id, part) VALUES (?, ?, ?)").run(eventID, message.id, 0) // 0 is primary, 1 is supporting diff --git a/db/ooye.db b/db/ooye.db index d878291da827fa7e2c5e35eeadf505bc34867d54..bae53f6c0cabf69cfac4dce2dd27882fc055d96a 100644 GIT binary patch delta 5650 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 delta 1072 zcmZWnT}%{L6ux(7?yxiWZ+8`!c3~FMYJDKZT_W8dwz~?^+R@gU_`DH=4+ejlQiu=T zLVZ$GDY{vW7-BIF{UNHVEyIAMp-nP^a*kG3@enxNzUAR&Ufzl z?)T*m%#ndP@=Yu}gb<3piU6#!;pWz@L+IT@^PELc#GFL3C^w6zd`66ke)fou@)F%m z@31)CXjQCPYs~!5oHYB)K5xA7&0rD^Oh;Q<@K@>H16@7cpMBcXRqX9Pa=f^l7L%_P zxatYoIH}TBzX6P;{VnPVeM4QMGL|eRUTYWWCL_A%9q6g^X3fvBJ}6|3+Do^q56x!f zT3n5qkw9xmEzqP0i-(9ujYb26A>_r`{e!`PxQXO3anlQL`AcYnk$@WiBJM4ff;bqE z2_zHp2Y@z8)$nzrS}N^Q-wz!I>Gkp5*u{tpQV?u83~OZQ$LHzhgbwbpGUFUv1wLA(&Zi<4r9VEigS1+@zuYKaErw_J~^g1o;sNu!;^oYM1S*TNYmu!*` zYdalPd2TOfsgRa--0=pyLD|pZBqOz)<2E(mI<<8^P6|TX1t;gDYVO%4lHseMQwEUz z4+d3k(I#oSLKJ!cMAiz?>9S1<=GyKP^EOEZSE&~t#!0qD%RB0LIYqLDzbjNyBu9K% zzm_v3uUCh)lbq()&OQdIfvVKERDA`=ah1F+X)T*LjaVm}k|i3H?_1NNB$ntEJ|HjC zGrUt=6a_ZOG5?d9g3yn_#P2NN=y0%aBCd4{5^wf38SArp7OKOGZFrsg2g6w-vQ6I! zl~)cL;f$Vk@T!GIs9n!NzTzR4?jXf)tKDVW5NjZC5OW{e?xr}l6*%Vha@q{)uQCKn66J@B-R?3E{mA R?D0PgA>r-)BZtRgF9EgAKbZgk diff --git a/matrix/file.js b/matrix/file.js index 137a096..077d527 100644 --- a/matrix/file.js +++ b/matrix/file.js @@ -58,5 +58,16 @@ function guildIcon(guild) { return `/icons/${guild.id}/${guild.icon}.png?size=${IMAGE_SIZE}` } +function userAvatar(user) { + return `/avatars/${user.id}/${user.avatar}.png?size=${IMAGE_SIZE}` +} + +function memberAvatar(guildID, user, member) { + if (!member.avatar) return userAvatar(user) + return `/guilds/${guildID}/users/${user.id}/avatars/${member.avatar}.png?size=${IMAGE_SIZE}` +} + module.exports.guildIcon = guildIcon +module.exports.userAvatar = userAvatar +module.exports.memberAvatar = memberAvatar module.exports.uploadDiscordFileToMxc = uploadDiscordFileToMxc