diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 725af1e2..a9e8c3e8 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -16,5 +16,6 @@ DON'T Repetitive violations of these guidelines might get your access to the repository restricted. - -If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from publicly challenging them and instead contact a Moderator on our Discord server or send an email to vendicated+conduct@riseup.net! +If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from vigilantism +and instead report the issue to a moderator! The best way is joining our [official Discord community](https://vencord.dev/discord) +and opening a modmail ticket. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 680f8375..6c133225 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,82 +1,55 @@ -# Contribution Guide +# Contributing to Vencord -First of all, thank you for contributing! :3 +Vencord is a community project and welcomes any kind of contribution from anyone! -To ensure your contribution is robust, please follow the below guide! +We have development documentation for new contributors, which can be found at . -For a friendly introduction to plugins, see [Megu's Plugin Guide!](docs/2_PLUGINS.md) +All contributions should be made in accordance with our [Code of Conduct](./CODE_OF_CONDUCT.md). -## Style Guide +## How to contribute -- This project has a very minimal .editorconfig. Make sure your editor supports this! - If you are using VSCode, it should automatically recommend you the extension; If not, - please install the Editorconfig extension -- Try to follow the formatting in the rest of the project and stay consistent -- Follow the file naming convention. File names should usually be camelCase, unless they export a Class - or React Component, in which case they should be PascalCase +Contributions can be sent via pull requests. If you're new to Git, check [this guide](https://opensource.com/article/19/7/create-pull-request-github). -## Contributing a Plugin +Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request. -Because plugins modify code directly, incompatibilities are a problem. +## Write a plugin -Thus, 3rd party plugins are not supported, instead all plugins are part of Vencord itself. -This way we can ensure compatibility and high quality patches. +Writing a plugin is the primary way to contribute. -Follow the below guide to make your first plugin! +Before starting your plugin: +- Check existing pull requests to see if someone is already working on a similar plugin +- Check our [plugin requests tracker](https://github.com/Vencord/plugin-requests/issues) to see if there is an existing request, or if the same idea has been rejected +- If there isn't an existing request, [open one](https://github.com/Vencord/plugin-requests/issues/new?assignees=&labels=&projects=&template=request.yml) yourself + and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it! +- Familarise yourself with our plugin rules below to ensure your plugin is not banned -### Finding the right module to patch +### Plugin Rules -If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools. -They come preinstalled and can be found as the "Components" tab in DevTools. -Use the Selector (top left) to select the UI Element. Now you can see all callbacks, props or jump to the source -directly. +- No simple slash command plugins like `/cat`. Instead, make a [user installable Discord bot](https://discord.com/developers/docs/change-log#userinstallable-apps-preview) +- No simple text replace plugins like Let me Google that for you. The TextReplace plugin can do this +- No raw DOM manipulation. Use proper patches and React +- No FakeDeafen or FakeMute +- No StereoMic +- No plugins that simply hide or redesign ui elements. This can be done with CSS +- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc) +- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones +- No plugins that require the user to enter their own API key +- Do not introduce new dependencies unless absolutely necessary and warranted -If it is anything else, or you're too lazy to use React DevTools, hit `CTRL + Shift + F` while in DevTools and -enter a search term, for example "getUser" to search all source files. -Look at the results until you find something promising. Set a breakpoint and trigger the execution of that part of Code to inspect arguments, locals, etc... +## Improve Vencord itself -### Writing a robust patch +If you have any ideas on how to improve Vencord itself, or want to propose a new plugin API, feel free to open a feature request so we can discuss. -##### "find" +Or if you notice any bugs or typos, feel free to fix them! -First you need to find a good `find` value. This should be a string that is unique to your module. -If you want to patch the `getUser` function, usually a good first try is `getUser:` or `function getUser()`, -depending on how the module is structured. Again, make sure this string is unique to your module and is not -found in any other module. To verify this, search for it in all bundles (CTRL + Shift + F) +## Contribute to our Documentation -##### "match" +The source code of our documentation is available at -This is the regex that will operate on the module found with "find". Just like in find, you should make sure -this only matches exactly the part you want to patch and no other parts in the file. +If you see anything outdated, incorrect or lacking, please fix it! +If you think a new page should be added, feel free to suggest it via an issue and we can discuss. -The easiest way to write and test your regex is the following: +## Help out users in our Discord community -- Get the ID of the module you want to patch. To do this, go to it in the sources tab and scroll up until you - see something like `447887: (e,t,n)=>{` (Obviously the number will differ). -- Now paste the following into the console: `Vencord.Webpack.wreq.m[447887].toString()` (Changing the number to your ID) -- Now either test regexes on this string in the console or use a tool like https://regex101.com - -Also pay attention to the following: - -- Never hardcode variable or parameter names or any other minified names. They will change in the future. The only Exception to this rule - are the react props parameter which seems to always be `e`, but even then only rely on this if it is necessary. - Instead, use one of the following approaches where applicable: - - Match 1 or 2 of any character: `.{1,2}`, for example to match the variable name in `var a=b`, `var (.{1,2})=` - - Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`, - `var .{1,2}=([^;]+);` - - If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters -- Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically - -#### "replace" - -This is the replacement for the match. This is the second argument to [String.replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), so refer to those docs for info. - -Never hardcode minified variable or parameter names here. Instead, use capture groups in your regex to capture the variable names -and use those in your replacement - -Make sure your replacement does not introduce any whitespace. While this might seem weird, random whitespace may mess up other patches. -This includes spaces, tabs and especially newlines - ---- - -And that's it! Now open a Pull Request with your Plugin +We have an open support channel in our [Discord community](https://vencord.dev/discord). +Helping out users there is always appreciated! The more, the merrier. diff --git a/browser/content.js b/browser/content.js index 4810fe3c..57964af6 100644 --- a/browser/content.js +++ b/browser/content.js @@ -2,23 +2,22 @@ if (typeof browser === "undefined") { var browser = chrome; } -const script = document.createElement("script"); -script.src = browser.runtime.getURL("dist/Vencord.js"); -script.id = "vencord-script"; -Object.assign(script.dataset, { - extensionBaseUrl: browser.runtime.getURL(""), - version: browser.runtime.getManifest().version -}); - const style = document.createElement("link"); style.type = "text/css"; style.rel = "stylesheet"; style.href = browser.runtime.getURL("dist/Vencord.css"); -document.documentElement.append(script); - document.addEventListener( "DOMContentLoaded", - () => document.documentElement.append(style), + () => { + document.documentElement.append(style); + window.postMessage({ + type: "vencord:meta", + meta: { + EXTENSION_VERSION: browser.runtime.getManifest().version, + EXTENSION_BASE_URL: browser.runtime.getURL(""), + } + }); + }, { once: true } ); diff --git a/browser/manifest.json b/browser/manifest.json index be09baca..1503f730 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -22,7 +22,15 @@ "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["content.js"], - "all_frames": true + "all_frames": true, + "world": "ISOLATED" + }, + { + "run_at": "document_start", + "matches": ["*://*.discord.com/*"], + "js": ["dist/Vencord.js"], + "all_frames": true, + "world": "MAIN" } ], diff --git a/browser/manifestv2.json b/browser/manifestv2.json index b28a468f..d6841b2d 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -22,7 +22,15 @@ "run_at": "document_start", "matches": ["*://*.discord.com/*"], "js": ["content.js"], - "all_frames": true + "all_frames": true, + "world": "ISOLATED" + }, + { + "run_at": "document_start", + "matches": ["*://*.discord.com/*"], + "js": ["dist/Vencord.js"], + "all_frames": true, + "world": "MAIN" } ], diff --git a/docs/1_INSTALLING.md b/docs/1_INSTALLING.md index 1f8a6ee6..9bcffe1a 100644 --- a/docs/1_INSTALLING.md +++ b/docs/1_INSTALLING.md @@ -51,13 +51,13 @@ Build Vencord: pnpm build ``` -Inject vencord into your client: +Inject Vencord into your client: ```shell pnpm inject ``` -Then fully close Discord from your taskbar or task manager, and restart it. Vencord should be injected - you can check this by looking for the Vencord section in Discord settings. +Then fully close Discord from your taskbar or task manager, and restart it. Vencord should be injected. You can check this by looking for the Vencord section in Discord settings. ## Updating Vencord @@ -71,9 +71,9 @@ To pull latest changes: git pull ``` -If this fails, you likely need to reset your local changes to vencord to resolve merge errors: +If this fails, you likely need to reset your local changes to Vencord to resolve merge errors: -> :exclamation: This command will remove any local changes you've made to vencord. Make sure you back up if you made any code changes you don't want to lose! +> :exclamation: This command will remove any local changes you've made to Vencord. Make sure you back up if you made any code changes you don't want to lose! ```shell git reset --hard diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 24c68c4e..7a041f1e 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -44,6 +44,11 @@ export interface ProfileBadge { position?: BadgePosition; /** The badge name to display, Discord uses this. Required for component badges */ key?: string; + + /** + * Allows dynamically returning multiple badges + */ + getBadges?(userInfo: BadgeUserArgs): ProfileBadge[]; } const Badges = new Set(); @@ -73,9 +78,16 @@ export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { if (!badge.shouldShow || badge.shouldShow(args)) { + const b = badge.getBadges + ? badge.getBadges(args).map(b => { + b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); + return b; + }) + : [{ ...badge, ...args }]; + badge.position === BadgePosition.START - ? badges.unshift({ ...badge, ...args }) - : badges.push({ ...badge, ...args }); + ? badges.unshift(...b) + : badges.push(...b); } } const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); diff --git a/src/components/ExpandableHeader.tsx b/src/components/ExpandableHeader.tsx index 84b06586..473dffaa 100644 --- a/src/components/ExpandableHeader.tsx +++ b/src/components/ExpandableHeader.tsx @@ -31,10 +31,20 @@ export interface ExpandableHeaderProps { headerText: string; children: React.ReactNode; buttons?: React.ReactNode[]; + forceOpen?: boolean; } -export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) { - const [showContent, setShowContent] = useState(defaultState); +export function ExpandableHeader({ + children, + onMoreClick, + buttons, + moreTooltipText, + onDropDownClick, + headerText, + defaultState = false, + forceOpen = false, +}: ExpandableHeaderProps) { + const [showContent, setShowContent] = useState(defaultState || forceOpen); return ( <> @@ -90,6 +100,7 @@ export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipTe setShowContent(v => !v); onDropDownClick?.(showContent); }} + disabled={forceOpen} > ); } + +export function SafetyIcon(props: IconProps) { + return ( + + + + + ); +} + +export function NotesIcon(props: IconProps) { + return ( + + + + + ); +} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 7c4683b0..9c4413f6 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -182,6 +182,8 @@ export default definePlugin({ }, getBadges(props: { userId: string; user?: User; guildId: string; }) { + if (!props) return []; + try { props.userId ??= props.user?.id!; diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 31fc71a9..85aadca1 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -109,9 +109,9 @@ interface ProfileModalProps { } const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); -const ProfileModal = findComponentByCodeLazy('"ProfileCustomizationPreview"'); +const ProfileModal = findComponentByCodeLazy("isTryItOutFlow:", "pendingThemeColors:", "avatarDecorationOverride:", ".CUSTOM_STATUS"); -const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); +const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); export default definePlugin({ name: "FakeProfileThemes", diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 78c1c5cf..bab69d89 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -222,6 +222,13 @@ export default definePlugin({ } ] }, + { + find: '="ActivityTrackingStore",', + replacement: { + match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/, + replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));` + } + }, { find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", replacement: { diff --git a/src/plugins/moreKaomoji/index.ts b/src/plugins/moreKaomoji/index.ts index 95991084..b5e33d96 100644 --- a/src/plugins/moreKaomoji/index.ts +++ b/src/plugins/moreKaomoji/index.ts @@ -27,12 +27,12 @@ export default definePlugin({ dependencies: ["CommandsAPI"], commands: [ { name: "dissatisfaction", description: " >﹏<" }, - { name: "smug", description: " ಠ_ಠ" }, - { name: "happy", description: " ヽ(´▽`)/" }, - { name: "crying", description: " ಥ_ಥ" }, - { name: "angry", description: " ヽ(`Д´)ノ" }, - { name: "anger", description: " ヽ(o`皿′o)ノ" }, - { name: "joy", description: " <( ̄︶ ̄)>" }, + { name: "smug", description: "ಠ_ಠ" }, + { name: "happy", description: "ヽ(´▽`)/" }, + { name: "crying", description: "ಥ_ಥ" }, + { name: "angry", description: "ヽ(`Д´)ノ" }, + { name: "anger", description: "ヽ(o`皿′o)ノ" }, + { name: "joy", description: "<( ̄︶ ̄)>" }, { name: "blush", description: "૮ ˶ᵔ ᵕ ᵔ˶ ა" }, { name: "confused", description: "(•ิ_•ิ)?" }, { name: "sleeping", description: "(ᴗ_ᴗ)" }, @@ -42,7 +42,7 @@ export default definePlugin({ ...data, options: [OptionalMessageOption], execute: opts => ({ - content: findOption(opts, "message", "") + data.description + content: findOption(opts, "message", "") + " " + data.description }) })) }); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 23a40ac3..0fbf41e9 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -49,7 +49,7 @@ export default definePlugin({ find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded replacement: { match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, - replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' + replace: '$self.isBotOrSelf(arguments[0].user)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' } }, { @@ -58,9 +58,24 @@ export default definePlugin({ match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" } + }, + { + find: ".MUTUAL_FRIENDS?(", + replacement: [ + { + match: /(?<=onItemSelect:\i,children:)(\i)\.map/, + replace: "[...$1, ...($self.isBotOrSelf(arguments[0].user) ? [] : [{section:'MUTUAL_GDMS',text:'Mutual Groups'}])].map" + }, + { + match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/, + replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" + } + ] } ], + isBotOrSelf: (user: User) => user.bot || user.id === UserStore.getCurrentUser().id, + renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => ( . */ +import { + findGroupChildrenByChildId, + NavContextMenuPatchCallback +} from "@api/ContextMenu"; import { definePluginSettings, migratePluginSettings } from "@api/Settings"; +import { CogWheel } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { Menu } from "@webpack/common"; +import { Guild } from "discord-types/general"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { @@ -73,48 +80,68 @@ const settings = definePluginSettings({ } }); +const makeContextMenuPatch: (shouldAddIcon: boolean) => NavContextMenuPatchCallback = (shouldAddIcon: boolean) => (children, { guild }: { guild: Guild, onClose(): void; }) => { + if (!guild) return; + + const group = findGroupChildrenByChildId("privacy", children); + group?.push( + applyDefaultSettings(guild.id)} + /> + ); +}; + +function applyDefaultSettings(guildId: string | null) { + if (guildId === "@me" || guildId === "null" || guildId == null) return; + updateGuildNotificationSettings(guildId, + { + muted: settings.store.guild, + suppress_everyone: settings.store.everyone, + suppress_roles: settings.store.role, + mute_scheduled_events: settings.store.events, + notify_highlights: settings.store.highlights ? 1 : 0 + }); + if (settings.store.messages !== 3) { + updateGuildNotificationSettings(guildId, + { + message_notifications: settings.store.messages, + }); + } + if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { + toggleShowAllChannels(guildId); + } +} + + migratePluginSettings("NewGuildSettings", "MuteNewGuild"); export default definePlugin({ name: "NewGuildSettings", description: "Automatically mute new servers and change various other settings upon joining", tags: ["MuteNewGuild", "mute", "server"], authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi, Devs.GabiRP], + contextMenus: { + "guild-context": makeContextMenuPatch(false), + "guild-header-popout": makeContextMenuPatch(true) + }, patches: [ { find: ",acceptInvite(", replacement: { match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/, - replace: (m, guildId) => `${m}$self.handleMute(${guildId});` + replace: (m, guildId) => `${m}$self.applyDefaultSettings(${guildId});` } }, { find: "{joinGuild:", replacement: { match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/, - replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});` + replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.applyDefaultSettings(${guildId});` } } ], settings, - - handleMute(guildId: string | null) { - if (guildId === "@me" || guildId === "null" || guildId == null) return; - updateGuildNotificationSettings(guildId, - { - muted: settings.store.guild, - suppress_everyone: settings.store.everyone, - suppress_roles: settings.store.role, - mute_scheduled_events: settings.store.events, - notify_highlights: settings.store.highlights ? 1 : 0 - }); - if (settings.store.messages !== 3) { - updateGuildNotificationSettings(guildId, - { - message_notifications: settings.store.messages, - }); - } - if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { - toggleShowAllChannels(guildId); - } - } + applyDefaultSettings }); diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index 869a6a1e..49770bbe 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -43,7 +43,7 @@ const Classes = proxyLazyWebpack(() => )) ) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>; -function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) { +function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) { const stns = settings.use(["permissionsSortOrder"]); const [rolePermissions, userPermissions] = useMemo(() => { @@ -95,6 +95,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G return ( diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index b27a3c2f..6401d945 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -20,15 +20,22 @@ import "./styles.css"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { SafetyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; +import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common"; +import { findByPropsLazy } from "@webpack"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import UserPermissions from "./components/UserPermissions"; import { getSortedRoles, sortPermissionOverwrites } from "./utils"; +const PopoutClasses = findByPropsLazy("container", "scroller", "list"); +const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text"); + export const enum PermissionsSortOrder { HighestRole, LowestRole @@ -168,10 +175,45 @@ export default definePlugin({ match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/, replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),` } + }, + { + find: ".VIEW_ALL_ROLES,", + replacement: { + match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/, + replace: "$&$self.ViewPermissionsButton(arguments[0])," + } } ], - UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && , + UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) => + !!guildMember && , + + ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => ( + ( + + + + )} + > + {popoutProps => ( + + + + )} + + ), { noop: true }), contextMenus: { "user-context": makeContextMenuPatch("roles", MenuItemParentType.User), diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 1c60098f..0ef961e5 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -149,3 +149,21 @@ .vc-permviewer-perms-perms-item .vc-info-icon:hover { color: var(--interactive-active); } + +/* copy pasted from discord cause impossible to webpack find */ +.vc-permviewer-role-button { + border-radius: var(--radius-xs); + background: var(--bg-mod-faint); + color: var(--interactive-normal); + border: 1px solid var(--border-faint); + /* stylelint-disable-next-line value-no-vendor-prefix */ + width: -moz-fit-content; + width: fit-content; + height: 24px; + padding: 4px +} + +.custom-profile-theme .vc-permviewer-role-button { + background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); + border-color: var(--profile-body-border-color) +} diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx index eef74d65..1dc76e9d 100644 --- a/src/plugins/platformIndicators/index.tsx +++ b/src/plugins/platformIndicators/index.tsx @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; +import "./style.css"; + +import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecoration, removeDecoration } from "@api/MessageDecorations"; import { Settings } from "@api/Settings"; @@ -27,7 +29,20 @@ import { findByPropsLazy, findStoreLazy } from "@webpack"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; -const SessionsStore = findStoreLazy("SessionsStore"); +export interface Session { + sessionId: string; + status: string; + active: boolean; + clientInfo: { + version: number; + os: string; + client: string; + }; +} + +const SessionsStore = findStoreLazy("SessionsStore") as { + getSessions(): Record; +}; function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) { return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => ( @@ -67,15 +82,11 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: return ; }; -const getStatus = (id: string): Record => PresenceStore.getState()?.clientStatuses?.[id]; - -const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { - if (!user || user.bot) return null; - +function ensureOwnStatus(user: User) { if (user.id === UserStore.getCurrentUser().id) { const sessions = SessionsStore.getSessions(); if (typeof sessions !== "object") return null; - const sortedSessions = Object.values(sessions).sort(({ status: a }: any, { status: b }: any) => { + const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => { if (a === b) return 0; if (a === "online") return 1; if (b === "online") return -1; @@ -84,7 +95,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma return 0; }); - const ownStatus = Object.values(sortedSessions).reduce((acc: any, curr: any) => { + const ownStatus = Object.values(sortedSessions).reduce((acc, curr) => { if (curr.clientInfo.client !== "unknown") acc[curr.clientInfo.client] = curr.status; return acc; @@ -93,6 +104,37 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma const { clientStatuses } = PresenceStore.getState(); clientStatuses[UserStore.getCurrentUser().id] = ownStatus; } +} + +function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] { + const user = UserStore.getUser(userId); + + if (!user || user.bot) return []; + + ensureOwnStatus(user); + + const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; + if (!status) return []; + + return Object.entries(status).map(([platform, status]) => ({ + component: () => ( + + + + ), + key: `vc-platform-indicator-${platform}` + })); +} + +const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => { + if (!user || user.bot) return null; + + ensureOwnStatus(user); const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record; if (!status) return null; @@ -112,17 +154,10 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma {icons} @@ -130,10 +165,8 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma }; const badge: ProfileBadge = { - component: p => , + getBadges, position: BadgePosition.START, - shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length, - key: "indicator" }; const indicatorLocations = { diff --git a/src/plugins/platformIndicators/style.css b/src/plugins/platformIndicators/style.css new file mode 100644 index 00000000..38ea5ef4 --- /dev/null +++ b/src/plugins/platformIndicators/style.css @@ -0,0 +1,7 @@ +.vc-platform-indicator { + display: inline-flex; + justify-content: center; + align-items: center; + vertical-align: top; + position: relative; +} diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx index 9882809f..43b0762a 100644 --- a/src/plugins/replaceGoogleSearch/index.tsx +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -13,13 +13,12 @@ import { Flex, Menu } from "@webpack/common"; const DefaultEngines = { Google: "https://www.google.com/search?q=", DuckDuckGo: "https://duckduckgo.com/", + Brave: "https://search.brave.com/search?q=", Bing: "https://www.bing.com/search?q=", Yahoo: "https://search.yahoo.com/search?p=", - GitHub: "https://github.com/search?q=", - Kagi: "https://kagi.com/search?q=", Yandex: "https://yandex.com/search/?text=", - AOL: "https://search.aol.com/aol/search?q=", - Baidu: "https://www.baidu.com/s?wd=", + GitHub: "https://github.com/search?q=", + Reddit: "https://www.reddit.com/search?q=", Wikipedia: "https://wikipedia.org/w/index.php?search=", } as const; @@ -55,7 +54,7 @@ function makeSearchItem(src: string) { key="search-text" id="vc-search-text" > - {Object.keys(Engines).map((engine, i) => { + {Object.keys(Engines).map(engine => { const key = "vc-search-content-" + engine; return (