From 46d2fa3dc95d4bbde1af25e456d1fcc9fafa5b05 Mon Sep 17 00:00:00 2001 From: everestmcarthur <280397636+everestmcarthur@users.noreply.github.com> Date: Sat, 1 Aug 2026 09:45:19 -0400 Subject: [PATCH] Deploy: Staff Tags --- 404.md | 52 ++++++++++++++++++++++++++++++++++++++++ README.md | 47 ++++++++++++++++++++++++++++++++++++ staff-tags/index.js | 1 + staff-tags/manifest.json | 1 + 4 files changed, 101 insertions(+) create mode 100644 404.md create mode 100644 README.md create mode 100644 staff-tags/index.js create mode 100644 staff-tags/manifest.json diff --git a/404.md b/404.md new file mode 100644 index 0000000..d29f09f --- /dev/null +++ b/404.md @@ -0,0 +1,52 @@ +--- +permalink: /404.html +--- +> **Note:** You accessed a link that returned a 404, probably by clicking one of the plugin links. You're supposed to copy the link address and add it into Revenge. + +# revenge-plugins + +A small collection of plugins for [Revenge](https://github.com/revenge-mod/revenge-bundle), the Discord Android client mod. + +## Installing a plugin + +1. Open Discord with Revenge installed, go to **Settings → Plugins**. +2. Tap the `+` button and paste the plugin's install link (see below). +3. Enable it. + +| Plugin | Description | Install link | +| --- | --- | --- | +| Staff Tags | Adds OWNER/ADMIN/STAFF/MOD-style tags next to members, based on their actual server permissions. Fully customizable: per-tag text, colors, gradients, and visibility. | `https://everestmcarthur.github.io/revenge-plugins/staff-tags/` | + +## About Staff Tags + +This is a rebuilt version of the old `staff-tags` plugin, which stopped working after Discord renamed some of the +internal components it depended on (`DisplayName`, `HeaderName`, `getTagProperties`) - when any one of those lookups +came back empty, the entire plugin used to fail to load, taking every tag down with it, everywhere. + +This version isolates each patch (chat tags, member list, profile, channel header) so that if Discord changes one of +them again in the future, only that specific surface stops showing tags instead of the whole plugin breaking. It also +adds: + +- Per-tag show/hide toggles +- Custom tag text +- Custom solid colors +- Gradient tags (member list & profile only - chat message tags are rendered from data rather than a + patchable element, so they always use a solid color) +- "Use top role color" as a fallback when no custom color is set + +Tags are computed from the built-in `computePermissions` API against real permission bits (Administrator, Manage +Server, Manage Messages, Kick/Ban, etc.), the same as the original plugin, not from role names. + +## Building locally + +```sh +npm install +npm run build +``` + +Output is written to `dist//`. + +## Contributing + +Issues and PRs are welcome, especially if a Discord update breaks a lookup a plugin relies on - `findByName` / +`findByProps` targets can and do change without notice. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd82aad --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# revenge-plugins + +A small collection of plugins for [Revenge](https://github.com/revenge-mod/revenge-bundle), the Discord Android client mod. + +## Installing a plugin + +1. Open Discord with Revenge installed, go to **Settings → Plugins**. +2. Tap the `+` button and paste the plugin's install link (see below). +3. Enable it. + +| Plugin | Description | Install link | +| --- | --- | --- | +| Staff Tags | Adds OWNER/ADMIN/STAFF/MOD-style tags next to members, based on their actual server permissions. Fully customizable: per-tag text, colors, gradients, and visibility. | `https://everestmcarthur.github.io/revenge-plugins/staff-tags/` | + +## About Staff Tags + +This is a rebuilt version of the old `staff-tags` plugin, which stopped working after Discord renamed some of the +internal components it depended on (`DisplayName`, `HeaderName`, `getTagProperties`) - when any one of those lookups +came back empty, the entire plugin used to fail to load, taking every tag down with it, everywhere. + +This version isolates each patch (chat tags, member list, profile, channel header) so that if Discord changes one of +them again in the future, only that specific surface stops showing tags instead of the whole plugin breaking. It also +adds: + +- Per-tag show/hide toggles +- Custom tag text +- Custom solid colors +- Gradient tags (member list & profile only - chat message tags are rendered from data rather than a + patchable element, so they always use a solid color) +- "Use top role color" as a fallback when no custom color is set + +Tags are computed from the built-in `computePermissions` API against real permission bits (Administrator, Manage +Server, Manage Messages, Kick/Ban, etc.), the same as the original plugin, not from role names. + +## Building locally + +```sh +npm install +npm run build +``` + +Output is written to `dist//`. + +## Contributing + +Issues and PRs are welcome, especially if a Discord update breaks a lookup a plugin relies on - `findByName` / +`findByProps` targets can and do change without notice. diff --git a/staff-tags/index.js b/staff-tags/index.js new file mode 100644 index 0000000..a3966ab --- /dev/null +++ b/staff-tags/index.js @@ -0,0 +1 @@ +(function(E,D,s,u,i,g,v,p,T,V){"use strict";const{Permissions:U}=i.constants,H=u.findByProps("computePermissions","canEveryoneRole"),K=u.findByStoreName("GuildMemberStore"),B=[{id:"webhook",defaultText:"WEBHOOK",defaultColor:"#99AAB5",condition:function(e,o,n){return!!n?.isNonUserBot?.()}},{id:"owner",defaultText:"OWNER",defaultColor:"#F0B232",condition:function(e,o,n){return!!e&&e.ownerId===n?.id}},{id:"admin",defaultText:"ADMIN",defaultColor:"#F23F42",permissions:["ADMINISTRATOR"]},{id:"staff",defaultText:"STAFF",defaultColor:"#23A55A",permissions:["MANAGE_GUILD","MANAGE_CHANNELS","MANAGE_ROLES","MANAGE_WEBHOOKS"]},{id:"mod",defaultText:"MOD",defaultColor:"#5865F2",permissions:["MANAGE_MESSAGES","KICK_MEMBERS","BAN_MEMBERS"]},{id:"vc_mod",defaultText:"VC Mod",defaultColor:"#1ABC9C",permissions:["MOVE_MEMBERS","MUTE_MEMBERS","DEAFEN_MEMBERS"]},{id:"chat_mod",defaultText:"Chat Mod",defaultColor:"#9B59B6",permissions:["MODERATE_MEMBERS"]}],S=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i;function A(e){return s.storage.tags??={},s.storage.tags[e]??={},s.storage.tags[e]}function W(e,o,n,r){if(o.useCustomColor&&o.color&&S.test(o.color))return o.color;if(s.storage.useRoleColor)try{const t=K?.getMember?.(n?.id,r?.id)?.colorString;if(t)return t}catch{}return e.defaultColor}function b(e,o,n){if(!n)return;let r=[];if(e)try{const t=H?.computePermissions?.({user:n,context:e,overwrites:o?.permissionOverwrites});t!=null&&(r=Object.entries(U).filter(function([,l]){return t&l}).map(function([l]){return l}))}catch{}for(const t of B){const l=A(t.id);if(l.enabled===!1)continue;const c=!!t.condition?.(e,o,n),a=!n.bot&&!!t.permissions?.some(function(ue){return r.includes(ue)});if(!c&&!a)continue;const d=W(t,l,e,n),f=i.chroma(d).get("lab.l")<70?v.rawColors.WHITE_500:v.rawColors.BLACK_500;let C;return l.useGradient&&(C=l.gradientColor&&S.test(l.gradientColor)?l.gradientColor:i.chroma(d).brighten(1.4).hex()),{id:t.id,text:l.text?.trim()||t.defaultText,textColor:f,backgroundColor:d,gradientColor:C,verified:!1}}}const N=u.findByName("getTagProperties",!1),j=u.findByStoreName("GuildStore"),z=u.findByStoreName("ChannelStore");function q(){return N?g.after("default",N,function([{message:e}],o){if(o?.tagType)return;const n=z?.getChannel(e?.channel_id),r=j?.getGuild(n?.guild_id),t=b(r,n,e?.author);if(t)return{...o,tagText:t.text,tagTextColor:t.textColor?i.ReactNative.processColor(i.chroma(t.textColor).hex()):void 0,tagBackgroundColor:i.ReactNative.processColor(i.chroma(t.backgroundColor).hex()),tagVerified:t.verified,tagType:void 0}}):function(){}}let h,M=!1;function J(){if(M)return h;M=!0;try{h=u.findByProps("colors","start","end")?.default??u.findByProps("colors","locations")?.default??null}catch{h=null}return h}const{View:Q,Text:k}=i.ReactNative,G={flexDirection:"row",alignItems:"center",borderRadius:4,paddingHorizontal:4,marginLeft:4,overflow:"hidden"};function I({text:e,textColor:o,backgroundColor:n,gradientColor:r,style:t}){const l={color:o,fontSize:11,fontWeight:"700"},c=r?J():null;return c?i.React.createElement(c,{colors:[n,r],start:{x:0,y:0},end:{x:1,y:0},style:[G,t]},i.React.createElement(k,{style:l},e)):i.React.createElement(Q,{style:[G,{backgroundColor:n},t]},i.React.createElement(k,{style:l},e))}const m=u.findByProps("getBotLabel"),X=u.findByStoreName("GuildStore"),Y=function([{guildId:e,user:o}],n){try{const r=n?.props?.label,t=p.findInReactTree(r,function(d){return Array.isArray(d?.props?.children)&&d.props.children.some(function(f){return typeof f=="string"||typeof f?.props?.children=="string"})});if(!t)return;const l=p.findInReactTree(t,function(d){return d?.type?.Types});if(l&&l.props?.type!==0)return;const c=X?.getGuild(e),a=b(c,void 0,o);if(!a)return;if(l){Object.assign(l.props,{type:0,text:a.text,textColor:a.textColor,backgroundColor:a.backgroundColor,verified:a.verified});return}Array.isArray(t.props.children)||(t.props.children=[t.props.children]),a.gradientColor?t.props.children.push(React.createElement(I,{text:a.text,textColor:a.textColor,backgroundColor:a.backgroundColor,gradientColor:a.gradientColor})):m&&t.props.children.push(React.createElement(m.default,{type:0,text:a.text,textColor:a.textColor,backgroundColor:a.backgroundColor,verified:a.verified}))}catch{}};function Z(){const e=[];return(u.findByTypeNameAll("UserRow")??[]).forEach(function(o){return e.push(g.after("type",o,Y))}),function(){return e.forEach(function(o){return o()})}}const _=u.findByName("DisplayName",!1),w=u.findByName("HeaderName",!1),$=u.findByProps("getBotLabel"),ee=u.findByStoreName("GuildStore"),te=u.findByStoreName("ChannelStore");function re(){const e=[];return w&&e.push(g.after("default",w,function([{channelId:o}],n){n?.props&&(n.props.channelId=o)})),_&&e.push(g.after("default",_,function([{guildId:o,channelId:n,user:r}],t){try{const l=p.findInReactTree(t,function(C){return C?.type?.Types});if(l&&l.props?.type!==0)return;const c=ee?.getGuild(o),a=te?.getChannel(n),d=b(c,a,r);if(!d)return;if(l){l.props={type:0,text:d.text,textColor:d.textColor,backgroundColor:d.backgroundColor,verified:d.verified};return}const f=p.findInReactTree(t,function(C){return C?.props?.style?.flexDirection==="row"});if(!Array.isArray(f?.props?.children))return;d.gradientColor?f.props.children.push(React.createElement(I,{style:{marginLeft:0},text:d.text,textColor:d.textColor,backgroundColor:d.backgroundColor,gradientColor:d.gradientColor})):$&&f.props.children.push(React.createElement($.default,{style:{marginLeft:0},type:0,text:d.text,textColor:d.textColor,backgroundColor:d.backgroundColor,verified:d.verified}))}catch{}})),function(){return e.forEach(function(o){return o()})}}const L=u.findByProps("getBotLabel");function oe(){return L?g.after("default",L,function([{text:e,textColor:o,backgroundColor:n}],r){const t=p.findInReactTree(r,function(l){return typeof l?.props?.children=="string"});t&&(e&&(t.props.children=e),o&&t.props.style?.push?.({color:o}),n&&r?.props?.style?.push?.({backgroundColor:n}))}):function(){}}const{ScrollView:ne,View:ae}=i.ReactNative,{FormSection:O,FormSwitchRow:x,FormInput:R}=V.Forms,F=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i;function le({id:e,defaultText:o,defaultColor:n}){const r=A(e);T.useProxy(r);const t=r.enabled!==!1,l=!!r.color&&!F.test(r.color),c=!!r.gradientColor&&!F.test(r.gradientColor);return i.React.createElement(O,{title:o},i.React.createElement(x,{label:"Show this tag",value:t,onValueChange:function(a){r.enabled=a}}),i.React.createElement(R,{title:"Tag text",placeholder:o,value:r.text??"",editable:t,onChange:function(a){r.text=a}}),i.React.createElement(x,{label:"Custom color",subLabel:`Default: ${n}`,value:!!r.useCustomColor,disabled:!t,onValueChange:function(a){r.useCustomColor=a}}),r.useCustomColor&&i.React.createElement(R,{title:"Color (hex)",placeholder:n,value:r.color??"",editable:t,onChange:function(a){r.color=a},helpText:l?"Invalid hex color, e.g. #5865F2 - falling back to default for now":void 0}),i.React.createElement(x,{label:"Gradient",subLabel:"Member list & profile only, chat tags stay solid",value:!!r.useGradient,disabled:!t,onValueChange:function(a){r.useGradient=a}}),r.useGradient&&i.React.createElement(R,{title:"Gradient color (hex)",placeholder:"Leave blank to auto-generate",value:r.gradientColor??"",editable:t,onChange:function(a){r.gradientColor=a},helpText:c?"Invalid hex color, leave blank to auto-generate":void 0}))}function ie(){return T.useProxy(s.storage),i.React.createElement(ne,{style:{flex:1}},i.React.createElement(O,{title:"General"},i.React.createElement(x,{label:"Use top role color",subLabel:"Used when a tag has no custom color set",value:!!s.storage.useRoleColor,onValueChange:function(e){s.storage.useRoleColor=e}})),B.map(function(e){return i.React.createElement(le,{key:e.id,id:e.id,defaultText:e.defaultText,defaultColor:e.defaultColor})}),i.React.createElement(ae,{style:{height:24}}))}let P=[];function y(e,o){try{return o()}catch(n){return D.logger.error(`[Staff Tags] Failed to apply the "${e}" patch, that surface will be skipped: ${n}`),function(){}}}var de={onLoad:function(){s.storage.useRoleColor??=!1,s.storage.tags??={},P=[y("tag",oe),y("chat",q),y("name",re),y("details",Z)]},onUnload:function(){return P.forEach(function(e){return e()})},settings:ie};return E.default=de,Object.defineProperty(E,"__esModule",{value:!0}),E})({},vendetta,vendetta.plugin,vendetta.metro,vendetta.metro.common,vendetta.patcher,vendetta.ui,vendetta.utils,vendetta.storage,vendetta.ui.components); diff --git a/staff-tags/manifest.json b/staff-tags/manifest.json new file mode 100644 index 0000000..c701d06 --- /dev/null +++ b/staff-tags/manifest.json @@ -0,0 +1 @@ +{"name":"Staff Tags","description":"Adds customizable tags (OWNER, ADMIN, STAFF, MOD, and more) next to members based on their server permissions. Supports per-tag colors, gradients, custom text, and visibility toggles.","authors":[{"name":"everestmcarthur"}],"main":"index.js","vendetta":{"icon":"ic_badge_staff"},"hash":"3ef1503145dbdc25a2c67b73e135ed8dd124a85de09b32b35f1773bbadebad4b"} \ No newline at end of file