diff --git a/README.md b/README.md index 6a4ac34..fa34744 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,48 @@ Shows tags for staff members in chat. -Currently shows the following: +### Setting Page and Buttons Descriptions and Actions. +| Option | Category | Action | +|--------------------------|--------------------|-------------------------------------------------------------------------------------------------| +| Note | Note | Note from developer(s). | +| Use Custom Tags settings | Preferences | If enabled, will use custom tags, and override default tags. | +| Use Top Color | Preferences | If enabled, will use highest role's color of the user's in a server, and override tag color. | +| Custom Tags | Customize | When clicked, will open a new page with list of custom tags. | +| [TAG_NAME] | Tags List | When clicked, will open a new page for editing said tag. | +| Add New Tags | Tags List | When clicked, will open a new page to add new tag. | +| Set Permissions | Tag Permissions | When clicked, will open a new page with list of permissions. | +| [PERMISSION_NAME] | Permisions List | When clicked, will add the permission to the tag's permissions, and return to editing tag page. | +| Save Custom Tag | Editing Custom Tag | When clicked, will save the tag and return to tag list page. | +| Delete Custom Tag | Editing Custom Tag | When clicked, will delete the tag and return to tag list page. | + + +### The plugin will also patch the following regardless if Custom Tags enabled or not: +- Webhook +- Bot - Owner -- Administrator (Based on if the member has the `ADMINISTRATOR` permission) -- Manager (Based on if the member has any of the following permissions: `MANAGE_GUILD`, `MANAGE_CHANNELS`, `MANAGE_ROLES`, `MANAGE_WEBHOOKS`) -- Moderator (Based on if the member has any of the following permissions: `MANAGE_MESSAGES`, `KICK_MEMBERS`, `BAN_MEMBERS`) -The tags can be themed, as they just use Discord colors: +### If Default settings used (Custom Tags Option Disabled) the plugin will shows the following: +| Display Name | Permissions | +|--------------|----------------------------------------------------------------------| +| ADMIN | `ADMINISTRATOR` | +| MANAGER | `MANAGE_GUILD`, `MANAGE_CHANNELS`, `MANAGE_ROLES`, `MANAGE_WEBHOOKS` | +| MOD | `MANAGE_MESSAGES`, `KICK_MEMBERS`, `BAN_MEMBERS` | + +Note: _The default tags can't be themed._ + +### Custom Tag Interface: +| Key | Type | Description | +|--------------|----------|------------------------------------------------------------------------------------------------------| +| Display Name | `String` | Text that will shown on the tag text. | +| Color | `String` | Valid HEX Code for tag color, if somehow the color is invalid, it'll use `#68b5d9` as Default Color. | +| Permissions | `Array` | Permissions List for the tag to be visible. Can be added with button called `Set Pemissions` | + + +### Developers +| Name | | | +|------------|-------------|-------------------------------------| +| Fiery | Author | Created Staff Tags plugin. | +| Angel~ | Contributor | Adding Customization to the plugin. | + -- Owner (`ORANGE_345`) -- Administrator (`RED_560`) -- Manager (`GREEN_345`) -- Moderator (`BLUE_345`) diff --git a/manifest.json b/manifest.json index 53cdebb..37dc3bb 100644 --- a/manifest.json +++ b/manifest.json @@ -1,14 +1,18 @@ -{ - "name": "Staff Tags", - "description": "Shows tags for staff members in chat.", - "authors": [ - { - "name": "Fiery", - "id": "890228870559698955" - } - ], - "main": "src/index.ts", - "vendetta": { - "icon": "ic_badge_certified_moderator" - } -} +{ + "name": "Staff Tags", + "description": "Shows tags for staff members in chat.", + "authors": [ + { + "name": "Fiery", + "id": "890228870559698955" + }, + { + "name": "Angel~", + "id": "692632336961110087" + } + ], + "main": "src/index.ts", + "vendetta": { + "icon": "ic_badge_certified_moderator" + } +} diff --git a/src/Settings.tsx b/src/Settings.tsx new file mode 100644 index 0000000..f4de524 --- /dev/null +++ b/src/Settings.tsx @@ -0,0 +1,310 @@ +import { storage } from "@vendetta/plugin"; +import { useProxy } from "@vendetta/storage"; +import { Forms, General } from "@vendetta/ui/components"; +import { getAssetIDByName } from "@vendetta/ui/assets"; +import { NavigationNative, constants, stylesheet, clipboard } from "@vendetta/metro/common"; +import { findByName, findByProps } from "@vendetta/metro"; +import { semanticColors, rawColors } from "@vendetta/ui"; +import { showToast } from "@vendetta/ui/toasts"; + +const { ScrollView, View, Text, TouchableOpacity, TextInput } = General; +const { FormLabel, FormArrow, FormRow, FormSwitch, FormSwitchRow, FormSection, FormDivider, FormInput } = Forms; + +// find stuff +const Icon = findByName("Icon"); +const useIsFocused = findByName("useIsFocused"); +const { BottomSheetFlatList } = findByProps("BottomSheetScrollView"); + +// Icons idk +const Add = getAssetIDByName("ic_add_24px"); +const Mod = getAssetIDByName("ic_arrow"); +const Remove = getAssetIDByName("ic_minus_circle_24px"); +const Checkmark = getAssetIDByName("Check"); +const Crossmark = getAssetIDByName("Small"); + + +// uhhh dont ask +const Capitalize = (str) => { return `${str[0].toUpperCase()}${str.toLowerCase().slice(1)}`; }; +const Clean = (str) => { return str.replaceAll('_', ' ') }; + + +const styles = stylesheet.createThemedStyleSheet({ + basicPad: { + paddingRight: 10, + marginBottom: 10, + letterSpacing: 0.25, + }, + header: { + color: semanticColors.HEADER_SECONDARY, + fontFamily: constants.Fonts.PRIMARY_BOLD, + paddingLeft: "3%", + fontSize: 24 + }, + sub: { + color: semanticColors.TEXT_POSITIVE, + fontFamily: constants.Fonts.DISPLAY_NORMAL, + paddingLeft: "4%", + fontSize: 18 + }, + flagsText: { + color: semanticColors.HEADER_SECONDARY, + fontFamily: constants.Fonts.PRIMARY_BOLD, + paddingLeft: "4%", + fontSize: 16 + } +}); + + +// storage.customTags + + +const placeholder = 'MissingNo'; +const HEX_regex = /[0-9A-Fa-f]{6}/; + +let switches = [ + { + id: `useCustomTags`, + default: false, + label: `Use Custom Tags settings`, + subLabel: `Use Custom Tags Settings for this plugin.`, + }, + { + id: `useRoleColor`, + default: false, + label: `Use top role color`, + subLabel: `This may result in unreadable tags as it isn't possible to change the tag's text color.`, + }, +] + +export default function Settings() { + useProxy(storage) + + + const { Permissions } = constants; + const unsorted_perm = Object.keys(Permissions); + const perm = unsorted_perm.sort() + + + const navigation = NavigationNative.useNavigation(); + useIsFocused(); + + storage.customTags ??= []; + + // console.log(storage) + + const CustomTagsClick = (inx) => { + + + + if(inx < 0) inx = 0; + + /* + name, color, perms[] + */ + + // console.log(inx) + + // console.log(storage.customTags[inx]) + + if(!storage.customTags[inx]) { + storage.customTags[inx] = { name: '' , color: '' , perms: [] }; + } + + let bob = storage.customTags[inx]; + + + // console.log(bob) + + + navigation.push("VendettaCustomPage", { + title: `Editing Custom Tag`, + render: () => <> + + + + bob.name = v} + /> + bob.color = v} + /> + + + + + { + `${bob.perms.join(" | ") || 'No Permissions Added.'}` + } + + + } + onPress={() => { + navigation.push("VendettaCustomPage", { + title: `Custom Tag Permissions`, + render: () => <> + + + { + perm?.map((component) => { + return (<> + } + onPress={() => { + if(bob?.perms?.includes(component)) { + showToast(`${component} removed!`, Crossmark) + bob?.perms.splice(component, 1) + } + else { + showToast(`${component} added!`, Checkmark) + bob?.perms.push(component) + } + navigation.pop() + navigation.pop() + CustomTagsClick(inx) + }} + /> + ) + }) + } + + + + + }) + }} + /> + + + } + onPress={() => { + + if(!bob?.name?.length) return showToast('Tag Name Cannot be Empty', Crossmark); + if(!bob?.perms?.length) return showToast('Tag Permissions Cannot be Empty', Crossmark); + + if(!bob.color.match(HEX_regex)) { + return showToast('Invalid HEX Code', Crossmark); + } + + if(inx > -1) { + storage.customTags[inx] = bob; + } + else { + storage.customTags.push(bob) + } + showToast(`Custom Tag Saved`, Checkmark) + navigation.pop() + navigation.pop() + ListCustomTags(true) + } + }/> + } + onPress={() => { + storage.customTags.splice(inx, 1); + navigation.pop() + navigation.pop() + ListCustomTags(true) + } + }/> + + + }) + }; + + const ListCustomTags = (newTag) => { + navigation.push("VendettaCustomPage", { + title: `List of Custom Tags`, + render: () => <> + + + { + storage.customTags?.map((comp, i) => { + return (<> + + } + onPress={ () => { + // console.log(comp) + let ifx = storage.customTags.findIndex(e => e.name == comp.name) + CustomTagsClick(ifx) + }} + /> + {i !== storage.customTags?.length - 1 && } + ); + }) + } + + + {; + let ifx = storage?.customTags?.length; + CustomTagsClick(ifx) + }}> + + + } + /> + + + + }) + }; + + return (<> + + + { + Capitalize("to enable custom tags, disable use default option and reload the plugin") + } + + + + { + switches.map((component, index) => { + return(<> + (storage[component.id] = value) } + /> + {index !== switches?.length - 1 && } + ) + }) + } + + + + + + + + } + /> + + + + + ); +} + diff --git a/src/index.ts b/src/index.ts index 4453693..2113307 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,105 +1,131 @@ -import { findByName, findByProps, findByStoreName } from "@vendetta/metro"; -import { ReactNative, chroma, constants, i18n } from "@vendetta/metro/common"; -import { after } from "@vendetta/patcher"; -import { storage } from "@vendetta/plugin"; -import Settings from "./ui/pages/Settings"; -import { rawColors } from "@vendetta/ui"; - -const RowManager = findByName("RowManager") - -// Stores -const GuildStore = findByStoreName("GuildStore") -const ChannelStore = findByStoreName("ChannelStore") - -// Permissions -const { Permissions } = constants -const { computePermissions } = findByProps("computePermissions", "canEveryoneRole") - -// Strings - -let unpatch - -const builtInTags = [ - i18n.Messages.AI_TAG, - //Messages.BOT_TAG_BOT, This is done in our own tags as webhooks use this - i18n.Messages.BOT_TAG_SERVER, - i18n.Messages.SYSTEM_DM_TAG_SYSTEM, - i18n.Messages.GUILD_AUTOMOD_USER_BADGE_TEXT -] - -const tags = [ - { - text: "WEBHOOK", - condition: (guild, channel, user) => user.isNonUserBot() - }, - - { - text: "OWNER", - color: rawColors.ORANGE_345, - condition: (guild, channel, user) => (guild?.ownerId === user.id) || (channel?.type === 3 && channel?.ownerId === user.id) - }, - { - text: i18n.Messages.BOT_TAG_BOT, - condition: (guild, channel, user) => user.bot - }, - { - text: "ADMIN", - color: rawColors.RED_560, - permissions: ["ADMINISTRATOR"] - }, - { - text: "MANAGER", - color: rawColors.GREEN_345, - permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES", "MANAGE_WEBHOOKS"] - }, - { - text: "MOD", - color: rawColors.BLUE_345, - permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"] - } -] - -export default { - onLoad: () => { - storage.useRoleColor ??= false - - unpatch = after("generate", RowManager.prototype, ([row], { message }) => { - // Return if it's not a message row - if (row.rowType !== 1) return - - if (!builtInTags.includes(message.tagText)) { - const { guildId, channelId } = message - - const guild = GuildStore.getGuild(guildId) - const channel = ChannelStore.getChannel(channelId) - - let permissions - if (guild) { - const permissionsInt = computePermissions({ - user: row.message.author, - context: guild, - overwrites: channel?.permissionOverwrites - }) - permissions = Object.entries(Permissions) - .map(([permission, permissionInt]: [string, bigint]) => - permissionsInt & permissionInt ? permission : "") - .filter(Boolean) - } - - for (const tag of tags) { - if (tag.condition?.(guild, channel, row.message.author) || - tag.permissions?.some(perm => permissions?.includes(perm))) { - message.tagText = tag.text - if (tag.color && !(storage.useRoleColor && row.message.colorString)) message.tagBackgroundColor = ReactNative.processColor(chroma(tag.color).hex()) - break - } - } - } - - // Use top role color for tag - if (storage.useRoleColor && row.message.colorString && message.tagText) message.tagBackgroundColor = ReactNative.processColor(chroma(row.message.colorString).hex()) - }) - }, - onUnload: () => unpatch(), - settings: Settings -} +import { findByName, findByProps, findByStoreName } from "@vendetta/metro"; +import { ReactNative, chroma, constants, i18n } from "@vendetta/metro/common"; +import { after } from "@vendetta/patcher"; +import { storage } from "@vendetta/plugin"; +import Settings from "./Settings"; +import { rawColors } from "@vendetta/ui"; + +const RowManager = findByName("RowManager") + +// Stores +const GuildStore = findByStoreName("GuildStore") +const ChannelStore = findByStoreName("ChannelStore") + +// Permissions +const { Permissions } = constants +const { computePermissions } = findByProps("computePermissions", "canEveryoneRole") + +// Strings + +let unpatch; + +const HEX_regex = /[0-9A-Fa-f]{6}/; + +const builtInTags = [ + i18n.Messages.AI_TAG, + //Messages.BOT_TAG_BOT, This is done in our own default_tags as webhooks use this + i18n.Messages.BOT_TAG_SERVER, + i18n.Messages.SYSTEM_DM_TAG_SYSTEM, + i18n.Messages.GUILD_AUTOMOD_USER_BADGE_TEXT +] + +// name String, color String, perms String[] + +const builtInReplace = [ + { + name: "WEBHOOK", + condition: (guild, channel, user) => user.isNonUserBot() + }, + { + name: "OWNER", + color: rawColors.ORANGE_345, + condition: (guild, channel, user) => (guild?.ownerId === user.id) || (channel?.type === 3 && channel?.ownerId === user.id) + }, + { + name: i18n.Messages.BOT_TAG_BOT, + condition: (guild, channel, user) => user.bot + } +] + +const default_tags = [ + ...builtInReplace, + { + name: "ADMIN", + color: '7f1c1e', + perms: ["ADMINISTRATOR"] + }, + { + name: "MANAGER", + color: "26b968", + perms: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES", "MANAGE_WEBHOOKS"] + }, + { + text: "MOD", + color: "00aafc", + perms: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"] + } +] + +export default { + onLoad: () => { + storage.useCustomTags ??= false + storage.useRoleColor ??= false + storage.customTags ??= []; + + unpatch = after("generate", RowManager.prototype, ([row], { message }) => { + // Return if it's not a message row + if (row.rowType !== 1) return + + if (!builtInTags.includes(message.tagText)) { + const { guildId, channelId } = message + + const guild = GuildStore.getGuild(guildId) + const channel = ChannelStore.getChannel(channelId) + + let permissions, usedTags; + + if (guild) { + const permissionsInt = computePermissions({ + user: row.message.author, + context: guild, + overwrites: channel?.permissionOverwrites + }) + permissions = Object.entries(Permissions) + .map(([permission, permissionInt]: [string, bigint]) => + permissionsInt & permissionInt ? permission : "") + .filter(Boolean) + } + + if(storage.useCustomTags) { + usedTags = [...builtInReplace, ...storage.customTags] + } + else { + usedTags = default_tags; + } + + // console.log(usedTags) + + for (const tag of usedTags) { + if ( tag.condition?.(guild, channel, row.message.author) || tag.perms?.some(p => permissions?.includes(p)) ) { + message.tagText = tag.name + if ( tag.color && !(storage.useRoleColor && row.message.colorString) ) { + + if(!tag.color.match(HEX_regex)) tag.color = "68b5d9"; + + if(!tag.color.startsWith('#')) tag.color = `#${tag.color}`; + + message.tagBackgroundColor = ReactNative.processColor(chroma(tag.color).hex()); + } + + break; + } + } + } + + // Use top role color for tag + if (storage.useRoleColor && row.message.colorString && message.tagText) message.tagBackgroundColor = ReactNative.processColor(chroma(row.message.colorString).hex()) + }) + }, + onUnload: () => unpatch(), + settings: Settings +} diff --git a/src/ui/pages/Settings.tsx b/src/ui/pages/Settings.tsx deleted file mode 100644 index bb43e3c..0000000 --- a/src/ui/pages/Settings.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { storage } from "@vendetta/plugin"; -import { useProxy } from "@vendetta/storage"; -import { Forms, General } from "@vendetta/ui/components"; - -const { ScrollView } = General -const { FormSection, FormSwitchRow } = Forms - -export default function Settings() { - useProxy(storage) - - return - - { - storage.useRoleColor = v; - }} - /> - - -}