Add Radial Status: colored ring around the avatar instead of the presence dot

This commit is contained in:
Ally Founder 2026-08-02 19:24:50 -04:00
parent 36d7d2a621
commit 511dff142c
7 changed files with 206 additions and 2 deletions

View file

@ -22,6 +22,7 @@ To install: open Discord with Revenge installed, go to **Settings → Plugins**,
| --- | --- | --- | --- |
| Staff Tags | Rebuilt | OWNER/ADMIN/STAFF/MOD-style tags next to members, computed from real server permissions. Per-tag text, colors, gradients, and visibility. | `https://rp.jarviscli.dev/staff-tags/install/` |
| Custom User Tags | New | Create your own tags (text + color) and assign them to specific users, independent of server permissions. Long-press a name to manage one, or add by user ID in settings. | `https://rp.jarviscli.dev/custom-user-tags/install/` |
| Radial Status | Revived | Replaces the small presence dot on an avatar with a colored ring instead - one color per status, adjustable size and thickness. Fork of [AngelW0lf's Radial Status](https://github.com/Angelix1/MP/tree/master/angel/radialstatus), BSD-3-Clause - dropped a phone-home mechanism that could silently uninstall the plugin for specific user IDs, and replaced non-functional size/thickness controls with real ones. | `https://rp.jarviscli.dev/radial-status/install/` |
| RoleColorEverywhere | Revived | A member's top role color shown in mentions, the typing indicator, voice channel names, member list role headers, and optionally message text. | `https://rp.jarviscli.dev/role-color-everywhere/install/` |
| PronounDB | Revived | Shows a user's pronouns in their profile, if they've set them at pronoundb.org (nothing to configure in the plugin itself). | `https://rp.jarviscli.dev/pronoun-db/install/` |
| FakeProfileThemesAndEffects | Revived | Profile theming and profile effects without Nitro. Fork of [shipwr3ckd's FPTE](https://github.com/shipwr3ckd/FPTE) - fixed a crash on every profile-editor open, confirmed via on-device diagnostics. | `https://rp.jarviscli.dev/fake-profile-themes-and-effects/install/` |
@ -52,8 +53,8 @@ To install: open Discord with Revenge installed, go to **Settings → Plugins**,
it didn't exist anywhere in the Revenge/Vendetta plugin ecosystem before.
**Licensing note:** everything in this repo is original work except `plugins/view-raw` (GPLv3, forked from
bwlok's ViewRaw) and `plugins/rose-utils` (CC-BY 4.0, forked from nexpid's NexxUtils) - the rest of the repo
isn't under either of those licenses.
bwlok's ViewRaw), `plugins/rose-utils` (CC-BY 4.0, forked from nexpid's NexxUtils), and `plugins/radial-status`
(BSD-3-Clause, forked from AngelW0lf's Radial Status) - the rest of the repo isn't under any of those licenses.
## Why some of these broke before, and what's different now

View file

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2024, Angel
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,9 @@
{
"name": "Radial Status",
"description": "Replaces the small presence dot on avatars with a colored ring instead - one color per status (online/idle/DND), fully configurable, plus size and thickness controls.",
"authors": [
{ "name": "everestmcarthur" },
{ "name": "AngelW0lf" }
],
"main": "src/index.ts"
}

View file

@ -0,0 +1,25 @@
import { logger } from "@vendetta";
import { storage } from "@vendetta/plugin";
import { applyPatches } from "@shared/lib/patcher";
import patchRing from "./patches/ringPatch";
import Settings from "./ui/Settings";
let unpatchAll: () => void = () => {};
export default {
onLoad: () => {
storage.colors ??= {
online: "#23A55A",
idle: "#F0B232",
dnd: "#F23F42"
};
storage.ringMult ??= 1.3;
storage.ringThickness ??= 2.5;
unpatchAll = applyPatches("Radial Status", logger, {
ring: patchRing
});
},
onUnload: () => unpatchAll(),
settings: Settings
};

View file

@ -0,0 +1,64 @@
import { General } from "@vendetta/ui/components";
import { before } from "@vendetta/patcher";
import { storage } from "@vendetta/plugin";
/**
* Forked from AngelW0lf's Radial Status (BSD-3-Clause, see LICENSE). The upstream original matched
* one hardcoded wrapper size (exactly 32x32, borderRadius 16) - generalized here to any square,
* fully-circular wrapper (borderRadius === width / 2) instead, since avatar sizes differ across
* member lists, DM lists, and profile popouts, and the two structural checks right below (a `user`
* prop with a string id, a `status` prop) already confirm this is the right element regardless of
* its exact pixel size.
*
* Not independently re-verified against a live decompiled build the way the rest of this repo's
* fixes are - this is a straight-line adaptation of upstream's own (previously working, per its
* changelog) logic. Every assumption below is guarded, so a shape mismatch just means the ring
* doesn't draw for that avatar, not a crash.
*/
export default function patchRing(): () => void {
if (!General?.View) return () => {};
return before("render", General.View, (args: any[]) => {
try {
const [wrapper] = args;
if (!wrapper || !Array.isArray(wrapper.style)) return;
const circleIdx = wrapper.style.findIndex(
(s: any) => s && typeof s.width === "number" && s.width === s.height && s.borderRadius === s.width / 2
);
if (circleIdx === -1) return;
const userProps = wrapper.children?.[1]?.props;
const presenceProps = wrapper.children?.[3]?.props;
if (!userProps || typeof userProps.user?.id !== "string") return;
if (!presenceProps || typeof presenceProps.status !== "string") return;
const colors = storage.colors ?? {};
const color = colors[presenceProps.status as string];
if (!color) return; // no color configured for this status - leave the native dot alone
const baseSize = wrapper.style[circleIdx].width;
const mult = storage.ringMult ?? 1.3;
const thickness = storage.ringThickness ?? 2.5;
presenceProps.size = 0;
presenceProps.isMobileOnline = false;
if (presenceProps.style) presenceProps.style.display = "none";
if (userProps.cutout?.nativeCutouts?.[0]) userProps.cutout.nativeCutouts[0].size = 0;
wrapper.style[circleIdx] = {
width: baseSize * mult,
height: baseSize * mult,
borderRadius: (baseSize / 2) * mult,
overflow: "hidden"
};
wrapper.style.push({
borderWidth: thickness * mult,
borderColor: color,
borderStyle: "solid"
});
} catch {
// Leave the native presence dot alone.
}
});
}

View file

@ -0,0 +1,61 @@
import { React } from "@vendetta/metro/common";
import { useProxy } from "@vendetta/storage";
import { storage } from "@vendetta/plugin";
import { Forms } from "@vendetta/ui/components";
import SettingsScaffold from "@shared/ui/SettingsScaffold";
import ColorInput from "@shared/ui/ColorInput";
import NoteBox from "@shared/ui/NoteBox";
const { FormSection, FormInput } = Forms;
const STATUSES: { key: string; label: string; defaultColor: string }[] = [
{ key: "online", label: "Online", defaultColor: "#23A55A" },
{ key: "idle", label: "Idle", defaultColor: "#F0B232" },
{ key: "dnd", label: "Do Not Disturb", defaultColor: "#F23F42" }
];
function numberInput(title: string, key: "ringMult" | "ringThickness", placeholder: string) {
return (
<FormInput
title={title}
placeholder={placeholder}
value={String(storage[key] ?? placeholder)}
onChange={(v: string) => {
const n = parseFloat(v);
if (!Number.isNaN(n) && n > 0) storage[key] = n;
}}
keyboardType="numeric"
/>
);
}
export default function Settings() {
useProxy(storage);
storage.colors ??= {};
return (
<SettingsScaffold>
<NoteBox>
Replaces the small presence dot on an avatar with a colored ring instead, per status
below. Leave a status blank to keep showing the normal dot for it.
</NoteBox>
<FormSection title="Ring colors">
{STATUSES.map(({ key, label, defaultColor }) => (
<ColorInput
key={key}
title={label}
value={storage.colors[key]}
placeholder={defaultColor}
onChange={(v: string) => {
storage.colors = { ...storage.colors, [key]: v };
}}
/>
))}
</FormSection>
<FormSection title="Ring size">
{numberInput("Size multiplier", "ringMult", "1.3")}
{numberInput("Ring thickness", "ringThickness", "2.5")}
</FormSection>
</SettingsScaffold>
);
}

View file

@ -1,4 +1,20 @@
{
"radial-status": {
"category": "Moderation & Appearance",
"status": "revived",
"accent": "#23A55A",
"tagline": "A colored ring around the avatar instead of a presence dot - similar to Platform Indicator, but for status.",
"note": "Fork of AngelW0lf's Radial Status (BSD-3-Clause, see LICENSE). Upstream had a phone-home mechanism - it fetched a remote blacklist on every load and could silently uninstall itself for specific user IDs the original author chose - dropped entirely here, no network requests at all. Upstream also matched one hardcoded wrapper size (32x32 only); generalized here to any fully-circular avatar wrapper, since avatar sizes differ across member lists, DM lists, and profile popouts. Upstream's settings screen had commented-out, non-functional size/thickness inputs and an unfinished joke placeholder button - both removed, with real working size and thickness controls in their place.",
"howItWorks": "Hides the small colored dot Discord shows on an avatar for online/idle/DND, and draws a colored ring around the whole avatar instead - similar in spirit to a platform indicator, but for presence status. Each status's color is configurable, along with the ring's size multiplier and thickness. A status left with no color configured just keeps showing the normal dot.",
"features": [
"Colored ring around the avatar instead of the small presence dot",
"Independent color per status: online, idle, do not disturb",
"Adjustable ring size multiplier and thickness",
"No network requests - upstream's remote blacklist/kill-switch mechanism was removed entirely"
],
"commands": [],
"limitations": "The wrapper-shape match this depends on hasn't been independently re-verified against a live decompiled build the way the rest of this repo's fixes are - it's a direct adaptation of upstream's own previously-working logic, generalized and guarded so a mismatch just means no ring is drawn rather than a crash."
},
"custom-user-tags": {
"category": "Moderation & Appearance",
"status": "new",