diff --git a/README.md b/README.md index c06d34e..8c297d7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/plugins/radial-status/LICENSE b/plugins/radial-status/LICENSE new file mode 100644 index 0000000..3744c08 --- /dev/null +++ b/plugins/radial-status/LICENSE @@ -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. diff --git a/plugins/radial-status/manifest.json b/plugins/radial-status/manifest.json new file mode 100644 index 0000000..d40aea3 --- /dev/null +++ b/plugins/radial-status/manifest.json @@ -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" +} diff --git a/plugins/radial-status/src/index.ts b/plugins/radial-status/src/index.ts new file mode 100644 index 0000000..daa1df7 --- /dev/null +++ b/plugins/radial-status/src/index.ts @@ -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 +}; diff --git a/plugins/radial-status/src/patches/ringPatch.ts b/plugins/radial-status/src/patches/ringPatch.ts new file mode 100644 index 0000000..e3e1fe4 --- /dev/null +++ b/plugins/radial-status/src/patches/ringPatch.ts @@ -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. + } + }); +} diff --git a/plugins/radial-status/src/ui/Settings.tsx b/plugins/radial-status/src/ui/Settings.tsx new file mode 100644 index 0000000..4777017 --- /dev/null +++ b/plugins/radial-status/src/ui/Settings.tsx @@ -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 ( + { + const n = parseFloat(v); + if (!Number.isNaN(n) && n > 0) storage[key] = n; + }} + keyboardType="numeric" + /> + ); +} + +export default function Settings() { + useProxy(storage); + storage.colors ??= {}; + + return ( + + + 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. + + + {STATUSES.map(({ key, label, defaultColor }) => ( + { + storage.colors = { ...storage.colors, [key]: v }; + }} + /> + ))} + + + {numberInput("Size multiplier", "ringMult", "1.3")} + {numberInput("Ring thickness", "ringThickness", "2.5")} + + + ); +} diff --git a/site/meta.json b/site/meta.json index c6e5c81..c8bdb2d 100644 --- a/site/meta.json +++ b/site/meta.json @@ -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",