Skip to content

Commit 2a4fd48

Browse files
committed
feat(PMP-PK): added more options (removing shorthand,...) and documentation
1 parent 75d0145 commit 2a4fd48

10 files changed

Lines changed: 170 additions & 14 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: minor
3+
---
4+
5+
added a limited compatibility with `pk;member` commands

.changeset/add_pmp_shorthands.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: minor
3+
---
4+
5+
added option to use shorthands to send a message with a Persona, for example `✨:test`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
rephrased the command describtion for `/usepmp` and made `/usepmp reset` actually reset the room association of the pmp

src/app/features/room/RoomInput.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
274274
pluralkitProxyMessageHandler.init();
275275
}, [pluralkitProxyMessageHandler]);
276276

277-
const pkCompatEnable = useSetting(settingsAtom, 'pkCompat');
277+
const [pkCompatEnable] = useSetting(settingsAtom, 'pkCompat');
278+
const [pmpProxyingEnable] = useSetting(settingsAtom, 'pmpProxying');
278279
const emojiBtnRef = useRef<HTMLButtonElement>(null);
279280
const micBtnRef = useRef<HTMLButtonElement>(null);
280281
const roomToParents = useAtomValue(roomToParentsAtom);
@@ -789,11 +790,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
789790
* and also allows clients to display an accurate preview of how the message will look with the profile applied while it's being composed.
790791
*/
791792
const perMessageProfile =
792-
pkCompatEnable && pluralkitProxyMessageHandler.isAProxiedMessage(plainText)
793+
pmpProxyingEnable && pluralkitProxyMessageHandler.isAProxiedMessage(plainText)
793794
? await pluralkitProxyMessageHandler.getPmpBasedOnMessage(plainText)
794795
: await getCurrentlyUsedPerMessageProfileForRoom(mx, roomId);
795796

796-
if (pkCompatEnable && pluralkitProxyMessageHandler.isAProxiedMessage(plainText))
797+
if (pmpProxyingEnable && pluralkitProxyMessageHandler.isAProxiedMessage(plainText))
797798
plainText = pluralkitProxyMessageHandler.stripProxyFromMessage(plainText) ?? plainText;
798799
if (perMessageProfile) {
799800
content['com.beeper.per_message_profile'] =
@@ -925,9 +926,10 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
925926
isMarkdown,
926927
canSendReaction,
927928
pkCompatEnable,
928-
pluralkitProxyMessageHandler,
929929
replyDraft,
930930
silentReply,
931+
pmpProxyingEnable,
932+
pluralkitProxyMessageHandler,
931933
scheduledTime,
932934
editingScheduledDelayId,
933935
nicknames,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { SequenceCard } from '$components/sequence-card';
2+
import { SettingTile } from '$components/setting-tile';
3+
import { useSetting } from '$state/hooks/settings';
4+
import { settingsAtom } from '$state/settings';
5+
import { Box, Switch, Text } from 'folds';
6+
import { SequenceCardStyle } from '../styles.css';
7+
8+
export function PKCompatSettings() {
9+
const [usePKCompat, setUsePKCompat] = useSetting(settingsAtom, 'pkCompat');
10+
const [usePmpProxying, setUsePmpProxying] = useSetting(settingsAtom, 'pmpProxying');
11+
12+
return (
13+
<Box direction="Column" gap="100">
14+
<Text size="L400">Limited Compatibility with PluralKit-like functions</Text>
15+
<SequenceCard
16+
className={SequenceCardStyle}
17+
variant="SurfaceVariant"
18+
direction="Column"
19+
gap="100"
20+
>
21+
<SettingTile
22+
title="Enable PK commands"
23+
description="If enabled, it will enable a few pk style commands, currently verry limited"
24+
after={
25+
<Switch
26+
variant="Primary"
27+
value={usePKCompat}
28+
onChange={setUsePKCompat}
29+
title={usePKCompat ? 'disable pk; commands' : 'enable pk; commands'}
30+
/>
31+
}
32+
/>
33+
<SettingTile
34+
title="Enable Shorthands"
35+
description="If enabled, you can use shorthands to use a Persona for one message only (eg. '✨:test')"
36+
after={
37+
<Switch
38+
variant="Primary"
39+
value={usePmpProxying}
40+
onChange={setUsePmpProxying}
41+
title={
42+
usePmpProxying
43+
? 'disable checking typed messages for shorthands'
44+
: 'enable checking typed messages for shorthands'
45+
}
46+
/>
47+
}
48+
/>
49+
</SequenceCard>
50+
</Box>
51+
);
52+
}

src/app/features/settings/Persona/ProfilesPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Page, PageHeader, PageNavContent } from '$components/page';
22
import { Box, IconButton, Icon, Icons, Text } from 'folds';
33
import { PerMessageProfileOverview } from './PerMessageProfileOverview';
4+
import { PKCompatSettings } from './PKCompat';
45

56
type PerMessageProfilePageProps = {
67
requestClose: () => void;
@@ -37,6 +38,7 @@ export function PerMessageProfilePage({ requestClose }: PerMessageProfilePagePro
3738
direction="Column"
3839
shrink="No"
3940
>
41+
<PKCompatSettings />
4042
<PerMessageProfileOverview />
4143
</Box>
4244
</PageNavContent>

src/app/hooks/useCommands.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { useOpenBugReportModal } from '$state/hooks/bugReportModal';
3232
import { createRoomEncryptionState } from '$components/create-room';
3333
import { parsePronounsInput } from '$utils/pronouns';
3434
import { sendFeedback } from '$utils/sendFeedbackToUser';
35+
import { PKitCommandMessageHandler } from '$plugins/pluralkit-handler/PKitCommandMessageHandler';
3536
import { useRoomNavigate } from './useRoomNavigate';
3637
import { enrichWidgetUrl } from './useRoomWidgets';
3738
import { useUserProfile } from './useUserProfile';
@@ -241,6 +242,7 @@ export enum Command {
241242
AddPerMessageProfileToAccount = 'addpmp',
242243
DeletePerMessageProfileFromAccount = 'delpmp',
243244
UsePerMessageProfile = 'usepmp',
245+
AssociateProxyPerMessageProfile = 'pmpproxy',
244246
Pronoun = 'pronoun',
245247
SPronoun = 'spronoun',
246248
Rainbow = 'rainbow',
@@ -276,6 +278,8 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
276278
const { navigateRoom } = useRoomNavigate();
277279
const [developerTools] = useSetting(settingsAtom, 'developerTools');
278280
const [enableMSC4268CMD] = useSetting(settingsAtom, 'enableMSC4268CMD');
281+
// helper for pkit commands
282+
const pkitcmdHandler = useMemo(() => new PKitCommandMessageHandler(mx, room), [mx, room]);
279283
const profile = useUserProfile(mx.getSafeUserId());
280284
const openBugReport = useOpenBugReportModal();
281285

@@ -601,6 +605,15 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
601605
});
602606
},
603607
},
608+
[Command.AssociateProxyPerMessageProfile]: {
609+
name: Command.AssociateProxyPerMessageProfile,
610+
description: 'Associate proxy with a profile. Example /pmpproxy id ✨:text',
611+
exe: async (payload) => {
612+
const pid: string = splitWithSpace(payload)[0];
613+
const proxy: string = splitWithSpace(payload)[1];
614+
pkitcmdHandler.handleMessage(`pk;member "${pid}" proxy ${proxy}`, true);
615+
},
616+
},
604617
[Command.MyRoomAvatar]: {
605618
name: Command.MyRoomAvatar,
606619
description: 'Change profile picture in current room. Example /myroomavatar mxc://xyzabc',
@@ -1437,6 +1450,7 @@ export const useCommands = (mx: MatrixClient, room: Room): CommandRecord => {
14371450
room,
14381451
profile.displayName,
14391452
profile.avatarUrl,
1453+
pkitcmdHandler,
14401454
developerTools,
14411455
enableMSC4268CMD,
14421456
openBugReport,

src/app/hooks/usePerMessageProfile.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,18 @@ export async function getAllPerMessageProfileProxies(
420420
return parr;
421421
}
422422

423+
export async function dropProxyAssociationForPMP(mx: MatrixClient, proxy: string) {
424+
const associations = getProxyAssociationMap(
425+
mx.getAccountData(`${ACCOUNT_DATA_PREFIX}.proxyassociation` as any)?.getContent()
426+
);
427+
if (!associations) return;
428+
associations.delete(proxy);
429+
mx.setAccountData(
430+
`${ACCOUNT_DATA_PREFIX}.proxyassociation` as any,
431+
{ associations: proxyAssociationsMapToObject(associations) } as any
432+
);
433+
}
434+
423435
/**
424436
*
425437
* 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.

src/app/plugins/pluralkit-handler/PKitCommandMessageHandler.ts

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
addOrUpdatePerMessageProfile,
33
associateProxyWithProfile,
4+
dropProxyAssociationForPMP,
45
getAllPerMessageProfiles,
56
getPerMessageProfileById,
67
PerMessageProfile,
@@ -10,11 +11,15 @@ import { MatrixClient, Room } from 'matrix-js-sdk';
1011

1112
const pkMemberRenameRegex = /^(pk;member)\s+"?([\w\s]+)"?\s*rename\s+"?([\w\s]+)"?$/;
1213
const pkMemberNewRegex = /^(pk;member)\s+new\s+"?([\w\s]+)"?$/;
13-
const pkMemberNewProxy = /^(pk;member)\s+"?([\w\s]+)"?\s+proxy\s+(.*text.*)$/;
14+
const pkMemberNewProxy = /^(pk;member)\s+"?([\w\s]+)"?\s+proxy(\s+add)?\s+(.*text.*)$/;
15+
const pkMemberRemoveProxy = /^(pk;member)\s+"?([\w\s]+)"?\s+proxy\s+remove\s+(.*text.*)$/;
1416

1517
const helpTextPkMemberNew = 'To create a new persona: pk;member new Yumi';
1618
const helpTextPkMemberRename = 'To rename a persona: pk;member "Rain Deer" rename "Micky Mouse"';
17-
const helpTextPkMemberNewProxy = 'To create a persona: pk;member Yumi proxy [text]';
19+
const helpTextPkMemberNewProxy = 'To add a shorthand to a persona: pk;member Yumi proxy [text]';
20+
const helpTextPkMemberRemoveProxy =
21+
'To remove a shorthand from a persona: pk;member Yumi proxy remove [text]';
22+
const helpTextPkMember = `Available 'pk;member' commands:\n${helpTextPkMemberNew}\n${helpTextPkMemberRename}\n${helpTextPkMemberNewProxy}\n${helpTextPkMemberRemoveProxy}`;
1823

1924
/**
2025
* build a regex to recognize proxies
@@ -37,15 +42,32 @@ function buildRegex(template: string): RegExp {
3742
* @author Rye
3843
*/
3944
export class PKitCommandMessageHandler {
45+
/**
46+
* the matrix client we use, is set in the constructor
47+
*
48+
* @private
49+
* @type {MatrixClient}
50+
* @memberof PKitCommandMessageHandler
51+
*/
4052
private readonly mx: MatrixClient;
4153

4254
private message = '';
4355

4456
private readonly room: Room;
4557

58+
/**
59+
* flag to interpret the name given as id
60+
*
61+
* @private
62+
* @type {boolean}
63+
* @memberof PKitCommandMessageHandler
64+
*/
65+
private useIdInsteadOfNameWherePossible: boolean;
66+
4667
public constructor(mx: MatrixClient, room: Room) {
4768
this.mx = mx;
4869
this.room = room;
70+
this.useIdInsteadOfNameWherePossible = false;
4971
}
5072

5173
/**
@@ -127,15 +149,41 @@ export class PKitCommandMessageHandler {
127149
this.mx.getSafeUserId()
128150
);
129151
addOrUpdatePerMessageProfile(this.mx, pmp);
152+
} else if (pkMemberRemoveProxy.test(this.message)) {
153+
const cmdParts = pkMemberRemoveProxy.exec(this.message);
154+
if (!cmdParts) return;
155+
const name = cmdParts[2];
156+
const matchAgainst = cmdParts[3];
157+
const pmpId = this.useIdInsteadOfNameWherePossible
158+
? name
159+
: (await getAllPerMessageProfiles(this.mx)).find((pmp) => pmp.name === name)?.id;
160+
if (!pmpId) {
161+
sendFeedback(
162+
`Persona with ${this.useIdInsteadOfNameWherePossible ? 'id' : 'name'} "${name}" doesn't exist in your records, ${helpTextPkMemberNew}`,
163+
this.room,
164+
this.mx.getSafeUserId()
165+
);
166+
return;
167+
}
168+
169+
dropProxyAssociationForPMP(this.mx, matchAgainst);
170+
171+
sendFeedback(
172+
`Persona with ${this.useIdInsteadOfNameWherePossible ? 'id' : 'name'} "${name}" (${pmpId}) is now no longer associated with ${matchAgainst}`,
173+
this.room,
174+
this.mx.getSafeUserId()
175+
);
130176
} else if (pkMemberNewProxy.test(this.message)) {
131177
const cmdParts = pkMemberNewProxy.exec(this.message);
132178
if (!cmdParts) return;
133179
const name = cmdParts[2];
134-
const matchAgainst = cmdParts[3];
135-
const pmpId = (await getAllPerMessageProfiles(this.mx)).find((pmp) => pmp.name === name)?.id;
180+
const matchAgainst = cmdParts[4];
181+
const pmpId = this.useIdInsteadOfNameWherePossible
182+
? name
183+
: (await getAllPerMessageProfiles(this.mx)).find((pmp) => pmp.name === name)?.id;
136184
if (!pmpId) {
137185
sendFeedback(
138-
`Persona with name "${name}" doesn't exist in your records, ${helpTextPkMemberNew}`,
186+
`Persona with ${this.useIdInsteadOfNameWherePossible ? 'id' : 'name'} "${name}" doesn't exist in your records, ${helpTextPkMemberNew}`,
139187
this.room,
140188
this.mx.getSafeUserId()
141189
);
@@ -144,7 +192,7 @@ export class PKitCommandMessageHandler {
144192
const matchAgainstRegExp = buildRegex(matchAgainst);
145193
associateProxyWithProfile(this.mx, pmpId, matchAgainst, matchAgainstRegExp, false);
146194
sendFeedback(
147-
`Persona with name "${name}" (${pmpId}) is now associated with ${matchAgainst}`,
195+
`Persona with ${this.useIdInsteadOfNameWherePossible ? 'id' : 'name'} "${name}" (${pmpId}) is now associated with ${matchAgainst}`,
148196
this.room,
149197
this.mx.getSafeUserId()
150198
);
@@ -155,7 +203,7 @@ export class PKitCommandMessageHandler {
155203
.map((pmp: PerMessageProfile) => `${pmp.id}: ${pmp.name ? pmp.name : '(empty name)'}`)
156204
.join('\n');
157205
sendFeedback(
158-
`You currently have the following persona set up:\n${stringListOfProfiles}\n\n${helpTextPkMemberNew}\n${helpTextPkMemberRename}\n${helpTextPkMemberNewProxy}`,
206+
`If you see this, you have messed up a command\n\nYou currently have the following persona set up:\n${stringListOfProfiles}\n\n${helpTextPkMember}`,
159207
this.room,
160208
this.mx.getSafeUserId()
161209
);
@@ -176,9 +224,18 @@ export class PKitCommandMessageHandler {
176224
* @param message the message we want to handle
177225
* @returns void
178226
*/
179-
public async handleMessage(message: string): Promise<void> {
227+
public async handleMessage(message: string, useId?: boolean): Promise<void> {
180228
this.message = message;
181-
if (!this.message.startsWith('pk')) return;
182-
if (this.message.startsWith('pk;member')) await this.memberHandler();
229+
this.useIdInsteadOfNameWherePossible = useId ?? false;
230+
if (!this.message.startsWith('pk;')) return;
231+
if (this.message.startsWith('pk;member')) {
232+
await this.memberHandler();
233+
return;
234+
}
235+
sendFeedback(
236+
`Command currently not supported, right now compatibility is limited and only a subset of pk;member is supported\n\n${helpTextPkMember}`,
237+
this.room,
238+
this.mx.getSafeUserId()
239+
);
183240
}
184241
}

src/app/state/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface Settings {
103103
* whether to enable pk compat
104104
*/
105105
pkCompat: boolean;
106+
pmpProxying: boolean;
106107

107108
// furry stuff
108109
renderAnimals: boolean;
@@ -187,6 +188,7 @@ const defaultSettings: Settings = {
187188
faviconForMentionsOnly: false,
188189
highlightMentions: true,
189190
pkCompat: false,
191+
pmpProxying: false,
190192

191193
// furry stuff
192194
renderAnimals: true,

0 commit comments

Comments
 (0)