Update global profiles for sims

This commit is contained in:
Cadence Ember
2025-12-01 16:48:11 +13:00
parent 493bc25602
commit c7313035a4
4 changed files with 85 additions and 31 deletions

View File

@@ -151,16 +151,9 @@ async function syncUser(messageID, author, roomID, shouldActuallySync) {
const mxid = await ensureSimJoined(pkMessage, roomID)
if (shouldActuallySync) {
// Build current profile data
// Build current profile data and sync if the hash has changed
const content = await memberToStateContent(pkMessage, author)
const currentHash = registerUser._hashProfileContent(content, 0)
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
// Only do the actual sync if the hash has changed since we last looked
if (existingHash !== currentHash) {
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
}
await registerUser._sendSyncUser(roomID, mxid, content, null)
}
return mxid

View File

@@ -23,6 +23,8 @@ let hasher = null
// @ts-ignore
require("xxhash-wasm")().then(h => hasher = h)
const supportsMsc4069 = api.versions().then(v => !!v?.unstable_features?.["org.matrix.msc4069"]).catch(() => false)
/**
* A sim is an account that is being simulated by the bridge to copy events from the other side.
* @param {DiscordTypes.APIUser} user
@@ -98,6 +100,23 @@ async function ensureSimJoined(user, roomID) {
return mxid
}
/**
* @param {DiscordTypes.APIUser} user
*/
async function userToGlobalProfile(user) {
const globalProfile = {}
globalProfile.displayname = user.username
if (user.global_name) globalProfile.displayname = user.global_name
if (user.avatar) {
const avatarPath = file.userAvatar(user) // the user avatar only
globalProfile.avatar_url = await file.uploadDiscordFileToMxc(avatarPath)
}
return globalProfile
}
/**
* @param {DiscordTypes.APIUser} user
* @param {Omit<DiscordTypes.APIGuildMember, "user"> | undefined} member
@@ -201,21 +220,45 @@ async function syncUser(user, member, channel, guild, roomID) {
const mxid = await ensureSimJoined(user, roomID)
const content = await memberToStateContent(user, member, guild.id)
const powerLevel = memberToPowerLevel(user, member, guild, channel)
const currentHash = _hashProfileContent(content, powerLevel)
await _sendSyncUser(roomID, mxid, content, powerLevel, {
// do not overwrite pre-existing data if we already have data and `member` is not accessible, because this would replace good data with bad data
allowOverwrite: !!member,
globalProfile: await userToGlobalProfile(user)
})
return mxid
}
/**
* @param {string} roomID
* @param {string} mxid
* @param {{displayname: string, avatar_url?: string}} content
* @param {number | null} powerLevel
* @param {{allowOverwrite?: boolean, globalProfile?: {displayname: string, avatar_url?: string}}} [options]
*/
async function _sendSyncUser(roomID, mxid, content, powerLevel, options) {
const currentHash = _hashProfileContent(content, powerLevel ?? 0)
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
// only do the actual sync if the hash has changed since we last looked
const hashHasChanged = existingHash !== currentHash
// however, do not overwrite pre-existing data if we already have data and `member` is not accessible, because this would replace good data with bad data
const wouldOverwritePreExisting = existingHash && !member
if (hashHasChanged && !wouldOverwritePreExisting) {
// always okay to add new data. for overwriting, restrict based on options.allowOverwrite, if present
const overwriteOkay = !existingHash || (options?.allowOverwrite ?? true)
if (hashHasChanged && overwriteOkay) {
const actions = []
// Update room member state
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
actions.push(api.sendState(roomID, "m.room.member", mxid, content, mxid))
// Update power levels
await api.setUserPower(roomID, mxid, powerLevel)
if (powerLevel != null) {
actions.push(api.setUserPower(roomID, mxid, powerLevel))
}
// Update global profile (if supported by server)
if (await supportsMsc4069) {
actions.push(api.profileSetDisplayname(mxid, options?.globalProfile?.displayname || content.displayname, true))
actions.push(api.profileSetAvatarUrl(mxid, options?.globalProfile?.avatar_url || content.avatar_url, true))
}
await Promise.all(actions)
// Update cached hash
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
}
return mxid
}
/**
@@ -254,5 +297,7 @@ module.exports._hashProfileContent = _hashProfileContent
module.exports.ensureSim = ensureSim
module.exports.ensureSimJoined = ensureSimJoined
module.exports.syncUser = syncUser
module.exports._sendSyncUser = _sendSyncUser
module.exports.syncAllUsersInRoom = syncAllUsersInRoom
module.exports._memberToPowerLevel = memberToPowerLevel
module.exports.supportsMsc4069 = supportsMsc4069

View File

@@ -128,16 +128,9 @@ async function syncUser(author, roomID, shouldActuallySync) {
const mxid = await ensureSimJoined(fakeUserID, author, roomID)
if (shouldActuallySync) {
// Build current profile data
// Build current profile data and sync if the hash has changed
const content = await authorToStateContent(author)
const currentHash = registerUser._hashProfileContent(content, 0)
const existingHash = select("sim_member", "hashed_profile_content", {room_id: roomID, mxid}).safeIntegers().pluck().get()
// Only do the actual sync if the hash has changed since we last looked
if (existingHash !== currentHash) {
await api.sendState(roomID, "m.room.member", mxid, content, mxid)
db.prepare("UPDATE sim_member SET hashed_profile_content = ? WHERE room_id = ? AND mxid = ?").run(currentHash, roomID, mxid)
}
await registerUser._sendSyncUser(roomID, mxid, content, null)
}
return mxid

View File

@@ -317,16 +317,34 @@ async function sendTyping(roomID, isTyping, mxid, duration) {
})
}
async function profileSetDisplayname(mxid, displayname) {
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid), {
/**
* @param {string} mxid
* @param {string} displayname
* @param {boolean} [inhibitPropagate]
*/
async function profileSetDisplayname(mxid, displayname, inhibitPropagate) {
const params = {}
if (inhibitPropagate) params["org.matrix.msc4069.propagate"] = false
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/displayname`, mxid, params), {
displayname
})
}
async function profileSetAvatarUrl(mxid, avatar_url) {
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid), {
avatar_url
})
/**
* @param {string} mxid
* @param {string} avatar_url
* @param {boolean} [inhibitPropagate]
*/
async function profileSetAvatarUrl(mxid, avatar_url, inhibitPropagate) {
const params = {}
if (inhibitPropagate) params["org.matrix.msc4069.propagate"] = false
if (avatar_url) {
await mreq.mreq("PUT", path(`/client/v3/profile/${mxid}/avatar_url`, mxid, params), {
avatar_url
})
} else {
await mreq.mreq("DELETE", path(`/client/v3/profile/${mxid}/avatar_url`, mxid, params))
}
}
/**
@@ -490,6 +508,10 @@ function getProfile(mxid) {
return mreq.mreq("GET", `/client/v3/profile/${mxid}`)
}
function versions() {
return mreq.mreq("GET", "/client/versions")
}
module.exports.path = path
module.exports.register = register
module.exports.createRoom = createRoom
@@ -526,3 +548,4 @@ module.exports.getAccountData = getAccountData
module.exports.setAccountData = setAccountData
module.exports.setPresence = setPresence
module.exports.getProfile = getProfile
module.exports.versions = versions