Use public Discord collectibles catalog.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 2s
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 2s
Remove local token setup in favor of the cached published catalog and document the collectible profile experience in the latest update notes.
This commit is contained in:
22
public/update/4.11.177.md
Normal file
22
public/update/4.11.177.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
version: 4.11.177
|
||||
title: Profile colors & banners
|
||||
date: 2026-08-23
|
||||
summary: Username colors now follow MSC4522 with separate dark and light theme pickers.
|
||||
---
|
||||
|
||||
## Profile colors (MSC4522)
|
||||
|
||||
Username colors are now stored on your Matrix profile instead of inside avatar images. In turn **will need to be updated in your Account settings**, this is now cross compatible with Element + Cinny
|
||||
|
||||
- Set **separate colors for dark and light themes** in Settings → Account → Profile.
|
||||
- Per-room username overrides are read from member profile data when available.
|
||||
|
||||

|
||||
|
||||
## Profile banners (MSC4133)
|
||||
|
||||
Profile banners now live on your Matrix profile via `m.banner_url` (MSC4133), not embedded in avatar metadata.
|
||||
|
||||
- Upload or remove a banner independently from your avatar.
|
||||
- Other clients that support MSC4133 can read your banner directly.
|
||||
@@ -1,22 +1,22 @@
|
||||
---
|
||||
version: 4.11.177
|
||||
title: Profile colors & banners
|
||||
date: 2026-08-23
|
||||
summary: Username colors now follow MSC4522 with separate dark and light theme pickers.
|
||||
version: 4.11.185
|
||||
title: Discord collectibles
|
||||
date: 2026-08-24
|
||||
summary: Add Discord-style profile effects, nameplates, and avatar decorations to Paarrot profiles.
|
||||
---
|
||||
|
||||
## Profile colors (MSC4522)
|
||||
## Discord collectibles
|
||||
|
||||
Username colors are now stored on your Matrix profile instead of inside avatar images. In turn **will need to be updated in your Account settings**, this is now cross compatible with Element + Cinny
|
||||
- Browse and apply profile effects, animated nameplates, and avatar decorations in Settings → Account → Profile.
|
||||
- Collectible assets are uploaded to your Matrix profile, so other Paarrot users can see them without Discord.
|
||||
- The catalog now comes from a public git, and no Discord token is required in Paarrot for the cosmetics.
|
||||
- Profile-effect previews play their intro and loop animations, with a mini profile preview for each effect.
|
||||
|
||||
- Set **separate colors for dark and light themes** in Settings → Account → Profile.
|
||||
- Per-room username overrides are read from member profile data when available.
|
||||
|
||||

|
||||
 
|
||||
|
||||
## Profile banners (MSC4133)
|
||||
## Profile presentation
|
||||
|
||||
Profile banners now live on your Matrix profile via `m.banner_url` (MSC4133), not embedded in avatar metadata.
|
||||
|
||||
- Upload or remove a banner independently from your avatar.
|
||||
- Other clients that support MSC4133 can read your banner directly.
|
||||
- Avatar decorations render in messages, profile cards, and the bottom sidebar avatar.
|
||||
- Nameplates to the right of messages, beneath timestamps and can be displayed behind the bottom sidebar avatar with opacity settings.
|
||||
- Customize the sidebar nameplate opacity in Settings → General → Collectibles.
|
||||
|
||||
BIN
public/update/images/discordcosmetics1.png
Normal file
BIN
public/update/images/discordcosmetics1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/update/images/discordcosmetics2.png
Normal file
BIN
public/update/images/discordcosmetics2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
@@ -338,9 +338,6 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
);
|
||||
const previewDisplayName = profile.displayName;
|
||||
const [kind, setKind] = useState<CollectibleKind>('profile_effect');
|
||||
const [hasToken, setHasToken] = useState(false);
|
||||
const [tokenInput, setTokenInput] = useState('');
|
||||
const [tokenSaving, setTokenSaving] = useState(false);
|
||||
const [catalogLoading, setCatalogLoading] = useState(false);
|
||||
const [catalogError, setCatalogError] = useState<string>();
|
||||
const [items, setItems] = useState<DiscordCollectibleItem[]>([]);
|
||||
@@ -352,12 +349,6 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
|
||||
const desktopAvailable = isElectronCollectiblesAvailable();
|
||||
|
||||
const refreshTokenState = useCallback(async () => {
|
||||
if (!desktopAvailable) return;
|
||||
const result = await window.electron!.discordCollectibles!.hasToken();
|
||||
setHasToken(Boolean(result?.data));
|
||||
}, [desktopAvailable]);
|
||||
|
||||
const loadCatalog = useCallback(async (force = false) => {
|
||||
if (!desktopAvailable) return;
|
||||
setCatalogLoading(true);
|
||||
@@ -376,14 +367,8 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
}, [desktopAvailable]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshTokenState();
|
||||
}, [refreshTokenState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasToken) {
|
||||
loadCatalog();
|
||||
}
|
||||
}, [hasToken, loadCatalog]);
|
||||
loadCatalog();
|
||||
}, [loadCatalog]);
|
||||
|
||||
const current = collectibles[KIND_FIELD[kind]];
|
||||
|
||||
@@ -416,27 +401,6 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
});
|
||||
}, [filteredGroups]);
|
||||
|
||||
const handleSaveToken = async () => {
|
||||
if (!desktopAvailable) return;
|
||||
setTokenSaving(true);
|
||||
setCatalogError(undefined);
|
||||
try {
|
||||
await window.electron!.discordCollectibles!.setToken(tokenInput);
|
||||
setTokenInput('');
|
||||
await refreshTokenState();
|
||||
} catch (e) {
|
||||
setCatalogError(e instanceof Error ? e.message : 'Failed to save token.');
|
||||
}
|
||||
setTokenSaving(false);
|
||||
};
|
||||
|
||||
const handleClearToken = async () => {
|
||||
if (!desktopAvailable) return;
|
||||
await window.electron!.discordCollectibles!.clearToken();
|
||||
setItems([]);
|
||||
await refreshTokenState();
|
||||
};
|
||||
|
||||
const handleApply = async (item: DiscordCollectibleItem) => {
|
||||
setApplyingId(item.id);
|
||||
setError(undefined);
|
||||
@@ -481,48 +445,24 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
return (
|
||||
<Box direction="Column" gap="300">
|
||||
<Text size="T200" style={{ opacity: 0.8 }}>
|
||||
Browse Discord shop collectibles live, then download and upload only what you pick to Matrix.
|
||||
Your Discord token stays on this device and is only used to list the shop catalog.
|
||||
Browse a community-published Discord collectibles catalog, then download and upload only what
|
||||
you pick to Matrix.
|
||||
</Text>
|
||||
|
||||
{!hasToken ? (
|
||||
<Box direction="Column" gap="200">
|
||||
<Text size="T300">Discord token (required to list the shop)</Text>
|
||||
<Input
|
||||
size="300"
|
||||
variant="Secondary"
|
||||
style={{ width: '100%' }}
|
||||
type="password"
|
||||
placeholder="User token or Bot xxxxx"
|
||||
value={tokenInput}
|
||||
onChange={(e) => setTokenInput(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
size="300"
|
||||
variant="Primary"
|
||||
fill="Solid"
|
||||
radii="300"
|
||||
onClick={handleSaveToken}
|
||||
disabled={tokenSaving || !tokenInput.trim()}
|
||||
>
|
||||
<Text size="B300">{tokenSaving ? 'Saving…' : 'Save token'}</Text>
|
||||
</Button>
|
||||
{catalogError && <Text size="T200" style={{ color: color.Critical.Main }}>{catalogError}</Text>}
|
||||
</Box>
|
||||
) : (
|
||||
<Box gap="200" alignItems="Center" wrap="Wrap">
|
||||
<Text size="T200" style={{ opacity: 0.8 }}>Discord token saved on this device.</Text>
|
||||
<Button size="300" variant="Secondary" fill="Soft" radii="300" onClick={() => loadCatalog(true)} disabled={catalogLoading}>
|
||||
<Text size="B300">{catalogLoading ? 'Refreshing…' : 'Refresh catalog'}</Text>
|
||||
</Button>
|
||||
<Button size="300" variant="Critical" fill="Soft" radii="300" onClick={handleClearToken}>
|
||||
<Text size="B300">Remove token</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box gap="200" alignItems="Center" wrap="Wrap">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Secondary"
|
||||
fill="Soft"
|
||||
radii="300"
|
||||
onClick={() => loadCatalog(true)}
|
||||
disabled={catalogLoading}
|
||||
>
|
||||
<Text size="B300">{catalogLoading ? 'Refreshing…' : 'Refresh catalog'}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{hasToken && (
|
||||
<>
|
||||
<>
|
||||
<Box gap="200" wrap="Wrap">
|
||||
{KIND_TABS.map((tab) => (
|
||||
<Button
|
||||
@@ -559,7 +499,7 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
{catalogLoading && (
|
||||
<Box gap="200" alignItems="Center">
|
||||
<Spinner size="100" variant="Secondary" />
|
||||
<Text size="T200">Loading catalog from Discord…</Text>
|
||||
<Text size="T200">Loading collectibles catalog…</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -606,8 +546,7 @@ export function CollectiblesSection({ collectibles, onApply }: CollectiblesSecti
|
||||
{!catalogLoading && filteredGroups.length === 0 && (
|
||||
<Text size="T200" style={{ opacity: 0.7 }}>No items match your search.</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
3
src/ext.d.ts
vendored
3
src/ext.d.ts
vendored
@@ -114,9 +114,6 @@ interface ElectronAPI {
|
||||
}) => void) => void;
|
||||
};
|
||||
discordCollectibles?: {
|
||||
hasToken: () => Promise<{ success: boolean; data?: boolean; error?: string }>;
|
||||
setToken: (token: string) => Promise<{ success: boolean; error?: string }>;
|
||||
clearToken: () => Promise<{ success: boolean; error?: string }>;
|
||||
fetchCatalog: (force?: boolean) => Promise<{
|
||||
success: boolean;
|
||||
data?: { items: unknown[]; fetchedAt: string };
|
||||
|
||||
Reference in New Issue
Block a user