1+ import { AccountDataCompatVersion , AccountDataEvent } from '$types/matrix/accountData' ;
12import { PronounSet } from '$utils/pronouns' ;
23import { MatrixClient } from 'matrix-js-sdk' ;
34
4- const ACCOUNT_DATA_PREFIX = 'fyi.cisnt.permessageprofile' ;
5+ const ACCOUNT_DATA_PREFIX = AccountDataEvent . SablePerProfileMessageProfiles ;
56
67/**
78 * a per message profile
@@ -26,6 +27,7 @@ export type PerMessageProfile = {
2627 * @see PronounSet for the format of the pronouns, and how to parse them from a string input
2728 */
2829 pronouns ?: PronounSet [ ] ;
30+ compat ?: AccountDataCompatVersion ;
2931} ;
3032
3133/**
@@ -96,6 +98,7 @@ type PerMessageProfileIndex = {
9698 * a list of all profile ids, used to list all profiles when the user wants to manage them.
9799 */
98100 profileIds : string [ ] ;
101+ compat : AccountDataCompatVersion ;
99102} ;
100103
101104/**
@@ -106,6 +109,64 @@ type PerMessageProfileRoomAssociation = {
106109 validUntil ?: number ;
107110} ;
108111
112+ /**
113+ * associating a profile by proxy
114+ * @author Rye
115+ */
116+ export type PerMessageProfileProxyAssociation = {
117+ /**
118+ * the profile associated with the proxy
119+ */
120+ profileId : string ;
121+ /**
122+ * regex (string representation of it) to handle the proxy
123+ */
124+ regexString : string ;
125+ /**
126+ * optional parameter to save when the proxy was added
127+ */
128+ setAt ?: number ;
129+ } ;
130+
131+ export type InternalPerMessageProfileProxyAssociation = {
132+ /**
133+ * the profile associated with the proxy
134+ */
135+ profileId : string ;
136+ /**
137+ * regex to handle the proxy
138+ */
139+ regex : RegExp ;
140+ /**
141+ * optional parameter to save when the proxy was added
142+ */
143+ setAt ?: number ;
144+ } ;
145+
146+ export function parsePerMessageProfileProxyAssociation (
147+ assoc : PerMessageProfileProxyAssociation
148+ ) : InternalPerMessageProfileProxyAssociation {
149+ return {
150+ profileId : assoc . profileId ,
151+ // we need to remove artifacts from the toString conversion
152+ regex : new RegExp ( assoc . regexString . slice ( 1 , - 1 ) ) ,
153+ setAt : assoc . setAt ,
154+ } satisfies InternalPerMessageProfileProxyAssociation ;
155+ }
156+
157+ type PerMessageProfileProxyAssociationWrapper = {
158+ /**
159+ * the associations saved in the wrapper
160+ */
161+ associations :
162+ | Map < string , PerMessageProfileProxyAssociation >
163+ | Record < string , PerMessageProfileProxyAssociation > ;
164+ /**
165+ * optional parameter to save compatibility information
166+ */
167+ compat ?: AccountDataCompatVersion ;
168+ } ;
169+
109170/**
110171 * the shape of the account data for room associations, which is a wrapper around a list of associations.
111172 * This is used to store the associations in account data, and allows us to easily add additional fields in the future if needed without breaking the existing data structure.
@@ -120,9 +181,14 @@ type PerMessageProfileRoomAssociationWrapper = {
120181 associations :
121182 | Map < string , PerMessageProfileRoomAssociation >
122183 | Record < string , PerMessageProfileRoomAssociation > ;
184+ compat ?: AccountDataCompatVersion ;
123185} ;
124186
125- // Helper to always get a Map from wrapper
187+ /**
188+ * unwrap a profile-room-associations-wrapper
189+ * @param wrapper the wrapper to unwrap
190+ * @returns unwrapped map for profile-room-associations
191+ */
126192function getAssociationsMap (
127193 wrapper ?: PerMessageProfileRoomAssociationWrapper
128194) : Map < string , PerMessageProfileRoomAssociation > {
@@ -138,6 +204,33 @@ function associationsMapToObject(
138204 return Object . fromEntries ( map ) ;
139205}
140206
207+ /**
208+ * helper function (similar to getAssociationsMap for Room associations)
209+ * @param wrapper the wrapper to unwrap
210+ * @returns unwrapped map of proxy associations
211+ */
212+ function getProxyAssociationMap (
213+ wrapper ?: PerMessageProfileProxyAssociationWrapper
214+ ) : Map < string , PerMessageProfileProxyAssociation > {
215+ if ( ! wrapper ?. associations ) return new Map ( ) ;
216+ if ( wrapper . associations instanceof Map ) return wrapper . associations ;
217+ return new Map ( Object . entries ( wrapper . associations ) ) ;
218+ }
219+
220+ function proxyAssociationsMapToObject (
221+ map : Map < string , PerMessageProfileProxyAssociation >
222+ ) : Record < string , PerMessageProfileProxyAssociation > {
223+ return Object . fromEntries ( map ) ;
224+ }
225+
226+ /**
227+ * getting a profile from the account data where the profile matches a given id
228+ *
229+ * @export
230+ * @param {MatrixClient } mx the matrix client
231+ * @param {string } id the profile id
232+ * @return {* } {(Promise<PerMessageProfile | undefined>)} the profile, with the profile Id, if it exists
233+ */
141234export async function getPerMessageProfileById (
142235 mx : MatrixClient ,
143236 id : string
@@ -146,24 +239,47 @@ export async function getPerMessageProfileById(
146239 return profile ? ( profile . getContent ( ) as unknown as PerMessageProfile ) : undefined ;
147240}
148241
242+ /**
243+ * getting an array of all PerMessageProfile's saved in the account data
244+ *
245+ * @export
246+ * @param {MatrixClient } mx the matrix client
247+ * @return {* } {Promise<PerMessageProfile[]>} a array containing all per-message-profiles saved
248+ */
149249export async function getAllPerMessageProfiles ( mx : MatrixClient ) : Promise < PerMessageProfile [ ] > {
150250 const profileData = mx . getAccountData ( `${ ACCOUNT_DATA_PREFIX } .index` as any ) ;
151251 const profileIds = ( profileData ?. getContent ( ) as PerMessageProfileIndex ) ?. profileIds || [ ] ;
152252 const profiles = await Promise . all ( profileIds . map ( ( id ) => getPerMessageProfileById ( mx , id ) ) ) ;
153253 return profiles . filter ( ( profile ) : profile is PerMessageProfile => profile !== undefined ) ;
154254}
155255
256+ /**
257+ * add or update a pmp
258+ * @param mx the matrix client
259+ * @param profile the profile to add/update
260+ * @returns void
261+ */
156262export function addOrUpdatePerMessageProfile ( mx : MatrixClient , profile : PerMessageProfile ) {
157263 const profileListIndex = mx . getAccountData ( `${ ACCOUNT_DATA_PREFIX } .index` as any ) ;
264+ const profileWithCompat = {
265+ ...profile ,
266+ compat : {
267+ version : 1 ,
268+ compatDate : '2026-03-26' ,
269+ } satisfies AccountDataCompatVersion ,
270+ } satisfies PerMessageProfile ;
158271 if ( profileListIndex ?. getContent ( ) ?. profileIds . includes ( profile . id ) ) {
159272 // profile already exists, just update it
160- return mx . setAccountData ( `${ ACCOUNT_DATA_PREFIX } .${ profile . id } ` as any , profile as any ) ;
273+ return mx . setAccountData (
274+ `${ ACCOUNT_DATA_PREFIX } .${ profile . id } ` as any ,
275+ profileWithCompat as any
276+ ) ;
161277 }
162278 // profile doesn't exist, add it to the index and then add the profile data
163279 const newProfileIds = [ ...( profileListIndex ?. getContent ( ) ?. profileIds || [ ] ) , profile . id ] ;
164280 return Promise . all ( [
165281 mx . setAccountData ( `${ ACCOUNT_DATA_PREFIX } .index` as any , { profileIds : newProfileIds } as any ) ,
166- mx . setAccountData ( `${ ACCOUNT_DATA_PREFIX } .${ profile . id } ` as any , profile as any ) ,
282+ mx . setAccountData ( `${ ACCOUNT_DATA_PREFIX } .${ profile . id } ` as any , profileWithCompat as any ) ,
167283 ] ) ;
168284}
169285
@@ -235,6 +351,77 @@ export async function setCurrentlyUsedPerMessageProfileIdForRoom(
235351}
236352
237353/**
354+ *
355+ * @param mx the matrix client
356+ * @param profileId the profile id which the prefix should be attached to
357+ * @param proxy the prefix to use as index
358+ * @param proxyRegExp the regex we can use to match the prefix
359+ * @param reset wheather to delete the prefix
360+ */
361+ export async function associateProxyWithProfile (
362+ mx : MatrixClient ,
363+ profileId : string | undefined ,
364+ proxy : string ,
365+ proxyRegExp : RegExp ,
366+ reset : boolean
367+ ) {
368+ const associations = getProxyAssociationMap (
369+ mx . getAccountData ( `${ ACCOUNT_DATA_PREFIX } .proxyassociation` as any ) ?. getContent ( )
370+ ) ;
371+
372+ if ( reset ) associations . delete ( proxy ) ;
373+
374+ if ( ! profileId ) throw new Error ( 'profileId might not be undefined' ) ;
375+ if ( profileId )
376+ associations . set ( proxy , {
377+ profileId,
378+ regexString : proxyRegExp . toString ( ) ,
379+ } satisfies PerMessageProfileProxyAssociation ) ;
380+ mx . setAccountData (
381+ `${ ACCOUNT_DATA_PREFIX } .proxyassociation` as any ,
382+ { associations : proxyAssociationsMapToObject ( associations ) } as any
383+ ) ;
384+ }
385+
386+ /**
387+ * get a profile based on a proxy
388+ * @param mx the matrix client
389+ * @param proxy the proxy to look for
390+ * @returns the profile, if any, associated with the prefix
391+ */
392+ export async function getProfileAssociatedWithProxy (
393+ mx : MatrixClient ,
394+ proxy : string
395+ ) : Promise < PerMessageProfile | undefined > {
396+ const profileId = getAssociationsMap (
397+ mx . getAccountData ( `${ ACCOUNT_DATA_PREFIX } .proxyassociation` as any ) ?. getContent ( )
398+ ) . get ( proxy ) ?. profileId ;
399+ if ( ! profileId ) return undefined ;
400+ return getPerMessageProfileById ( mx , profileId ) ;
401+ }
402+
403+ /**
404+ *
405+ *
406+ * @export
407+ * @param {MatrixClient } mx the matrix client
408+ * @return {* } {Promise<PerMessageProfileProxyAssociation[]>}
409+ */
410+ export async function getAllPerMessageProfileProxies (
411+ mx : MatrixClient
412+ ) : Promise < PerMessageProfileProxyAssociation [ ] > {
413+ const cont : PerMessageProfileProxyAssociationWrapper | undefined = mx
414+ . getAccountData ( `${ ACCOUNT_DATA_PREFIX } .proxyassociation` as any )
415+ ?. getContent ( ) ;
416+ if ( ! cont ) return [ ] ;
417+ const pmap = getProxyAssociationMap ( cont ) ;
418+ const parr = new Array < PerMessageProfileProxyAssociation > ( ) ;
419+ pmap . values ( ) . forEach ( ( v ) => parr . push ( v ) ) ;
420+ return parr ;
421+ }
422+
423+ /**
424+ *
238425 * drops all room associations for a profile, used when deleting a profile to make sure there are no dangling associations left that point to a non existing profile, which could cause issues when trying to apply the profile to a message in a room that still has an association for the deleted profile.
239426 *
240427 * @param {MatrixClient } mx the matrix client
0 commit comments