1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-09 17:36:23 +00:00

Merge branch 'main' into main

This commit is contained in:
Aidan 2024-11-20 14:26:09 -05:00 committed by GitHub
commit 134b70002d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
105 changed files with 689 additions and 392 deletions

View file

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.10.5",
"version": "1.10.7",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -35,6 +35,7 @@
"testTsc": "tsc --noEmit"
},
"dependencies": {
"@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",

View file

@ -16,6 +16,9 @@ importers:
.:
dependencies:
'@intrnl/xxhash64':
specifier: ^0.1.2
version: 0.1.2
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.5
version: 0.3.5
@ -537,6 +540,9 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'}
'@intrnl/xxhash64@0.1.2':
resolution: {integrity: sha512-1+lx7j99fdph+uy3EnjQyr39KQZ7LP56+aWOr6finJWpgYpvb7XrhFUqDwnEk/wpPC98nCjAT6RulpW3crWjlg==}
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@ -2939,6 +2945,8 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {}
'@intrnl/xxhash64@0.1.2': {}
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1

View file

@ -225,7 +225,7 @@ page.on("console", async e => {
plugin,
type,
id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
error: await maybeGetError(e.args()[3])
});

View file

@ -18,9 +18,8 @@
import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord";
import { getIntlMessage, getTheme, Theme } from "@utils/discord";
import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon
aria-label={i18n.Messages.GUILD_OWNER}
aria-label={getIntlMessage("GUILD_OWNER")}
{...props}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"

View file

@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
}
try {
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
if (!parsed.find) throw new Error("No 'find' field");
if (!parsed.replacement) throw new Error("No 'replacement' field");

View file

@ -27,13 +27,16 @@ export async function loadLazyChunks() {
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
let foundCssDebuggingLoad = false;
async function searchAndLoadLazyChunks(factoryCode: string) {
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
const shouldForceDefer = false;
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
@ -45,6 +48,16 @@ export async function loadLazyChunks() {
let invalidChunkGroup = false;
for (const id of chunkIds) {
if (hasCssDebuggingLoad) {
if (chunkIds.length > 1) {
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
}
invalidChunks.add(id);
invalidChunkGroup = true;
break;
}
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))

View file

@ -31,7 +31,7 @@ export default definePlugin({
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
replace: "$&vencordProps=$1,"
}, {
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
}
]

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Cyn],
patches: [
{
find: ".Messages.REMOVE_ATTACHMENT_BODY",
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
replacement: {
match: /(?<=.container\)?,children:)(\[.+?\])/,
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",

View file

@ -27,7 +27,7 @@ export default definePlugin({
{
find: '"Message Username"',
replacement: {
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
}
}

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [
{
find: ".Messages.EDIT_TEXTAREA_HELP",
find: "#{intl::EDIT_TEXTAREA_HELP}",
replacement: {
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
replace: (match, args) => "" +

View file

@ -24,9 +24,9 @@ export default definePlugin({
description: "API to add buttons to message popovers.",
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
patches: [{
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.string\(\i\.\i#{intl::MESSAGE_ACTION_REPLY}.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
}
}],

View file

@ -25,16 +25,16 @@ export default definePlugin({
description: "Api required for plugins that modify the server list",
patches: [
{
find: "Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
replacement: {
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
}
},
{
find: "Messages.SERVERS,children",
find: "#{intl::SERVERS}),children",
replacement: {
match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/,
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
}
}

View file

@ -59,15 +59,7 @@ export default definePlugin({
replace: "$&return;"
}
]
},
{
find: ".installedLogHooks)",
replacement: {
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
}
},
}
],
startAt: StartAt.Init,

View file

@ -25,8 +25,9 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
import VencordTab from "@components/VencordSettings/VencordTab";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { i18n, React } from "@webpack/common";
import { React } from "@webpack/common";
import gitHash from "~git-hash";
@ -57,20 +58,20 @@ export default definePlugin({
]
},
{
find: "Messages.ACTIVITY_SETTINGS",
find: ".SEARCH_NO_RESULTS&&0===",
replacement: [
{
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
},
{
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.open($1);return;"
@ -148,13 +149,18 @@ export default definePlugin({
if (!header) return;
const names = {
top: i18n.Messages.USER_SETTINGS,
aboveNitro: i18n.Messages.BILLING_SETTINGS,
belowNitro: i18n.Messages.APP_SETTINGS,
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
};
return header === names[settingsLocation];
try {
const names = {
top: getIntlMessage("USER_SETTINGS"),
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
belowNitro: getIntlMessage("APP_SETTINGS"),
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
};
return header === names[settingsLocation];
} catch {
return firstChild === "PREMIUM";
}
},
patchedSettings: new WeakSet(),

View file

@ -147,9 +147,9 @@ export default definePlugin({
settings,
patches: [{
find: ".BEGINNING_DM.format",
find: "#{intl::BEGINNING_DM}",
replacement: {
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
}
}],

View file

@ -69,7 +69,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
group: true,
replacement: [
{

View file

@ -41,7 +41,7 @@ export default definePlugin({
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"

View file

@ -28,10 +28,18 @@ export default definePlugin({
patches: [
{
find: 'action:"EXPAND_ROLES"',
replacement: {
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
}
replacement: [
{
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
},
{
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
// which makes the collapse button never show up and calculation never occur
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
replace: isExpanded => "false"
}
]
}
]
});

View file

@ -86,9 +86,9 @@ export default definePlugin({
}
},
{
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
replacement: {
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
},
},

View file

@ -36,7 +36,7 @@ export default definePlugin({
settings,
patches: [
{
find: "BAN_CONFIRM_TITLE.",
find: "#{intl::BAN_CONFIRM_TITLE}",
replacement: {
match: /src:\i\("?\d+"?\)/g,
replace: "src:$self.source"

View file

@ -18,9 +18,10 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
import { FluxDispatcher, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
@ -172,7 +173,7 @@ export default definePlugin({
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
replace: "$self.shouldShowTransition(arguments[0])&&"
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
@ -205,7 +206,7 @@ export default definePlugin({
}
},
{
find: ".Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
predicate: () => settings.store.closeAllHomeButton,
replacement: {
// Close all folders when clicking the home button
@ -274,12 +275,16 @@ export default definePlugin({
},
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
}
return true;
};
return child => {
if (isBetterFolders) {
try {
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
} catch (e) {
console.error(e);
}
}
return true;
};
},
makeGuildsBarTreeFilter(isBetterFolders: boolean) {

View file

@ -34,9 +34,9 @@ export default definePlugin({
},
},
{
find: ".Messages.GIF,",
find: "#{intl::GIF}",
replacement: {
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::GIF}\))(?=,[^}]*\}=(\i))/,
replace:
// rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2",

View file

@ -63,9 +63,9 @@ export default definePlugin({
}
},
{
find: "Messages.NOTE_PLACEHOLDER",
find: "#{intl::NOTE_PLACEHOLDER}",
replacement: {
match: /\.NOTE_PLACEHOLDER,/,
match: /#{intl::NOTE_PLACEHOLDER}\),/,
replace: "$&spellCheck:!$self.noSpellCheck,"
}
}

View file

@ -47,7 +47,7 @@ export default definePlugin({
},
{
find: ".ADD_ROLE_A11Y_LABEL",
find: "#{intl::ADD_ROLE_A11Y_LABEL}",
all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,

View file

@ -60,7 +60,7 @@ export default definePlugin({
patches: [
{
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
replacement: [
// Replace children with a single label with state
{

View file

@ -5,8 +5,9 @@
*/
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { getIntlMessage } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common";
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
import Plugins from "~plugins";
@ -48,7 +49,7 @@ export default function PluginsSubmenu() {
query={query}
onChange={setQuery}
ref={ref}
placeholder={i18n.Messages.SEARCH}
placeholder={getIntlMessage("SEARCH")}
/>
)}
/>

View file

@ -7,10 +7,11 @@
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
import PluginsSubmenu from "./PluginsSubmenu";
@ -111,7 +112,7 @@ export default definePlugin({
predicate: () => settings.store.disableFade
},
{ // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)(),"
@ -119,7 +120,7 @@ export default definePlugin({
predicate: () => settings.store.eagerLoad
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [
{
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
@ -159,7 +160,7 @@ export default definePlugin({
if (item.section === "HEADER") {
items.push({ label: item.label, items: [] });
} else if (item.section === "DIVIDER") {
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
} else {
items.at(-1)!.items.push(item);
}

View file

@ -14,7 +14,7 @@ import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",

View file

@ -130,6 +130,27 @@ export default definePlugin({
replace: ""
}
},
// Zustand section
{
find: "[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.",
replacement: [
{
match: /&&console\.warn\("\[DEPRECATED\] Passing a vanilla store will be unsupported in a future version\. Instead use `import { useStore } from 'zustand'`\."\)/,
replace: ""
},
{
match: /console\.warn\("\[DEPRECATED\] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`\. They can be imported from 'zustand\/traditional'\. https:\/\/github\.com\/pmndrs\/zustand\/discussions\/1937"\),/,
replace: ""
}
]
},
{
find: "[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead.",
replacement: {
match: /console\.warn\("\[DEPRECATED\] `getStorage`, `serialize` and `deserialize` options are deprecated\. Use `storage` option instead\."\),/,
replace: ""
}
},
// Patches discords generic logger function
{
find: "Σ:",
@ -147,5 +168,5 @@ export default definePlugin({
replace: "$self.NoopLogger()"
}
}
],
]
});

View file

@ -18,6 +18,7 @@
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
@ -104,6 +105,7 @@ function makeShortcuts() {
canonicalizeMatch,
canonicalizeReplace,
canonicalizeReplacement,
runtimeHashMessageKey,
fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Obsidian, Devs.Nuckyz],
patches: [
{
find: ".Messages.PREVIEW_BYTES_LEFT.format(",
find: "#{intl::PREVIEW_BYTES_LEFT}",
replacement: {
match: /\.footerGap.+?url:\i,fileName:\i,fileSize:\i}\),(?<=fileContents:(\i),bytesLeft:(\i).+?)/g,
replace: "$&$self.addCopyButton({fileContents:$1,bytesLeft:$2}),"

View file

@ -67,7 +67,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
replacement: {
match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this,$1);"

View file

@ -41,7 +41,7 @@ export default definePlugin({
{
find: "DefaultCustomizationSections",
replacement: {
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
replace: "$self.DecorSection(),"
}
},

View file

@ -93,7 +93,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate(
} as AuthorizationState),
{
name: "decor-auth",
getStorage: () => indexedDBStorage,
storage: indexedDBStorage,
partialize: state => ({ tokens: state.tokens }),
onRehydrateStorage: () => state => state?.init()
}

View file

@ -95,24 +95,39 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set: any,
} as UsersDecorationsState)));
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
try {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
useEffect(() => {
const destructor = useUsersDecorationsStore.subscribe(
state => {
if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id);
if (!newDecorAvatarDecoration) return;
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
}
);
useEffect(() => {
const destructor = (() => {
try {
return useUsersDecorationsStore.subscribe(
state => {
if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id);
if (!newDecorAvatarDecoration) return;
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
}
);
} catch {
return () => { };
}
})();
if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id);
}
return destructor;
}, []);
try {
if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id);
}
} catch { }
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
return destructor;
}, []);
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
} catch (e) {
console.error(e);
}
return null;
}

View file

@ -5,7 +5,8 @@
*/
import { PlusIcon } from "@components/Icons";
import { i18n, Text } from "@webpack/common";
import { getIntlMessage } from "@utils/discord";
import { Text } from "@webpack/common";
import { HTMLProps } from "react";
import { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridCreate(props: DecorationGridCreateProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.CREATE}
{getIntlMessage("CREATE")}
</Text>
</DecorationGridItem >;
}

View file

@ -5,7 +5,8 @@
*/
import { NoEntrySignIcon } from "@components/Icons";
import { i18n, Text } from "@webpack/common";
import { getIntlMessage } from "@utils/discord";
import { Text } from "@webpack/common";
import { HTMLProps } from "react";
import { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridNone(props: DecorationGridNoneProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.NONE}
{getIntlMessage("NONE")}
</Text>
</DecorationGridItem >;
}

View file

@ -10,6 +10,7 @@ import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Queue } from "@utils/Queue";
import { findComponentByCodeLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
@ -49,6 +50,8 @@ interface SectionHeaderProps {
section: Section;
}
const fetchAuthorsQueue = new Queue();
function SectionHeader({ section }: SectionHeaderProps) {
const hasSubtitle = typeof section.subtitle !== "undefined";
const hasAuthorIds = typeof section.authorIds !== "undefined";
@ -56,17 +59,18 @@ function SectionHeader({ section }: SectionHeaderProps) {
const [authors, setAuthors] = useState<User[]>([]);
useEffect(() => {
(async () => {
fetchAuthorsQueue.push(async () => {
if (!section.authorIds) return;
for (const authorId of section.authorIds) {
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId);
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId).catch(() => null);
if (author == null) continue;
setAuthors(authors => [...authors, author]);
}
})();
});
}, [section.authorIds]);
return <div>
<Flex>
<Forms.FormTitle style={{ flexGrow: 1 }}>{section.title}</Forms.FormTitle>

View file

@ -20,7 +20,7 @@ import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"),
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"),
HelpMessage: filters.byCode(".iconDiv")
});
@ -119,8 +119,8 @@ function CreateDecorationModal(props: ModalProps) {
/>
</div>
</div>
<Forms.FormText type="description" className={Margins.bottom16}>
<br />You can receive updates on your decoration's review by joining <Link
<HelpMessage messageType={HelpMessageTypes.INFO} className={Margins.bottom8}>
To receive updates on your decoration's review, join <Link
href={`https://discord.gg/${INVITE_KEY}`}
onClick={async e => {
e.preventDefault();
@ -138,8 +138,8 @@ function CreateDecorationModal(props: ModalProps) {
}}
>
Decor's Discord server
</Link>.
</Forms.FormText>
</Link> and allow direct messages.
</HelpMessage>
</ErrorBoundary>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.Nuckyz],
patches: [
{
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
find: "#{intl::BOT_CALL_IDLE_DISCONNECT_2}",
replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
replace: ";return;"

View file

@ -20,11 +20,11 @@ import { addPreEditListener, addPreSendListener, removePreEditListener, removePr
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { getCurrentGuild, getEmojiURL } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, Patch } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
@ -285,7 +285,7 @@ export default definePlugin({
},
// Remove boost requirements to stream with high quality
{
find: "STREAM_FPS_OPTION.format",
find: "#{intl::STREAM_FPS_OPTION}",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: {
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
@ -356,7 +356,7 @@ export default definePlugin({
]
},
{
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
find: "#{intl::STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformStickers,
replacement: [
{
@ -381,7 +381,7 @@ export default definePlugin({
}
},
{
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
find: "#{intl::EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformEmojis,
replacement: {
// Add the fake nitro emoji notice
@ -920,7 +920,7 @@ export default definePlugin({
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
@ -953,7 +953,7 @@ export default definePlugin({
hasBypass = true;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);

View file

@ -108,10 +108,10 @@ interface ProfileModalProps {
isTryItOutFlow: boolean;
}
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER");
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
const requireColorPicker = extractAndLoadChunksLazy(["#{intl::USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON}"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
export default definePlugin({
name: "FakeProfileThemes",
@ -126,9 +126,9 @@ export default definePlugin({
}
},
{
find: ".USER_SETTINGS_RESET_PROFILE_THEME",
find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}",
replacement: {
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\)}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
}
}

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux],
patches: [
{
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /,isOwner:(\i),/,
replace: ",_isOwner:$1=$self.isGuildOwner(e),"

View file

@ -26,7 +26,7 @@ export default definePlugin({
{
find: ".PANEL}),nicknameIcons",
replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/,
match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id)}\)}\)/,
replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})"
}
},
@ -34,7 +34,7 @@ export default definePlugin({
{
find: "action:\"PRESS_APP_CONNECTION\"",
replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false}),"
}
}

View file

@ -19,10 +19,11 @@
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react";
import definePlugin from "@utils/types";
import { filters, findByPropsLazy, waitFor } from "@webpack";
import { ChannelStore, ContextMenuApi, i18n, UserStore } from "@webpack/common";
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
@ -41,7 +42,7 @@ function MessageMenu({ message, channel, onHeightUpdate }) {
return useMessageMenu({
navId: "message-actions",
ariaLabel: i18n.Messages.MESSAGE_UTILITIES_A11Y_LABEL,
ariaLabel: getIntlMessage("MESSAGE_UTILITIES_A11Y_LABEL"),
message,
channel,
@ -72,7 +73,7 @@ const contextMenuPatch: NavContextMenuPatchCallback = (children, props: MessageA
const group = findGroupChildrenByChildId("devmode-copy-id", children, true);
group?.push(
CopyIdMenuItem({ id: props.message.author.id, label: i18n.Messages.COPY_ID_AUTHOR })
CopyIdMenuItem({ id: props.message.author.id, label: getIntlMessage("COPY_ID_AUTHOR") })
);
};

View file

@ -92,7 +92,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: {
match: /this\.renderNameZone\(\).+?children:\[/,
replace: "$&$self.GameActivityToggleButton(),"

View file

@ -166,7 +166,7 @@ export default definePlugin({
patches: [
{
find: "Messages.WELCOME_CTA_LABEL",
find: "#{intl::WELCOME_CTA_LABEL}",
replacement: {
match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"

View file

@ -260,9 +260,9 @@ export default definePlugin({
}
},
{
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
replacement: {
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
}
},

View file

@ -32,7 +32,7 @@ export default definePlugin({
patches: [
// Counts header
{
find: ".FRIENDS_ALL_HEADER",
find: "#{intl::FRIENDS_ALL_HEADER}",
replacement: {
match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/,
replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
@ -48,9 +48,9 @@ export default definePlugin({
},
// Sections header
{
find: ".FRIENDS_SECTION_ONLINE",
find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: {
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.Messages\.BLOCKED\}\)/,
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
},
},
@ -117,7 +117,7 @@ export default definePlugin({
wrapSort(comparator: Function, row: any) {
return row.type === 5
? -UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0
? -(UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0)
: comparator(row);
},

View file

@ -111,7 +111,7 @@ export default definePlugin({
patches: [
{
// Indicator
find: ".Messages.MESSAGE_EDITED,",
find: "#{intl::MESSAGE_EDITED}",
replacement: {
match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"

View file

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
import { ChannelRouter, ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export interface LogoutEvent {
type: "LOGOUT";
@ -45,6 +45,16 @@ export default definePlugin({
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
authors: [Devs.Nuckyz],
patches: [
{
find: '"Switching accounts"',
replacement: {
match: /goHomeAfterSwitching:\i/,
replace: "goHomeAfterSwitching:!1"
}
}
],
flux: {
LOGOUT(e: LogoutEvent) {
({ isSwitchingAccount } = e);
@ -55,7 +65,11 @@ export default definePlugin({
isSwitchingAccount = false;
if (previousCache?.channelId) {
ChannelRouter.transitionToChannel(previousCache.channelId);
if (ChannelStore.hasChannel(previousCache.channelId)) {
ChannelRouter.transitionToChannel(previousCache.channelId);
} else {
NavigationRouter.transitionToGuild("@me");
}
}
},

View file

@ -62,7 +62,7 @@ export default definePlugin({
patches: [
{
find: ".LOADING_DID_YOU_KNOW",
find: "#{intl::LOADING_DID_YOU_KNOW}",
replacement: [
{
match: /"_loadingText".+?(?=(\i)\[.{0,10}\.random)/,

View file

@ -74,7 +74,7 @@ export default definePlugin({
{
find: ".invitesDisabledTooltip",
replacement: {
match: /\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100}(?=])/,
match: /#{intl::VIEW_AS_ROLES_MENTIONS_WARNING}.{0,100}(?=])/,
replace: "$&,$self.renderTooltip(arguments[0].guild)"
},
predicate: () => settings.store.toolTip

View file

@ -24,12 +24,12 @@ import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { Message } from "discord-types/general";
import overlayStyle from "./deleteStyleOverlay.css?managed";
@ -43,7 +43,6 @@ interface MLMessage extends Message {
}
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
const getMessage = findByCodeLazy('replace(/^\\n+|\\n+$/g,"")');
function addDeleteStyle() {
if (Settings.plugins.MessageLogger.deleteStyle === "text") {
@ -178,7 +177,7 @@ export default definePlugin({
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
<span className={styles.edited}>{" "}({getIntlMessage("MESSAGE_EDITED")})</span>
</Timestamp>
</div>
))}
@ -312,9 +311,33 @@ export default definePlugin({
);
},
Messages: proxyLazy(() => ({
DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
})),
// DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
// TODO: Find a better way to generate intl messages
DELETED_MESSAGE_COUNT: () => ({
ast: [[
6,
"count",
{
"=0": ["No deleted messages"],
one: [
[
1,
"count"
],
" deleted message"
],
other: [
[
1,
"count"
],
" deleted messages"
]
},
0,
"cardinal"
]]
}),
patches: [
{
@ -445,7 +468,7 @@ export default definePlugin({
{
// Message content renderer
find: "Messages.MESSAGE_EDITED,\")\"",
find: "#{intl::MESSAGE_EDITED}",
replacement: [
{
// Render editHistory in the deepest div for message content
@ -497,7 +520,7 @@ export default definePlugin({
},
{
// Message group rendering
find: "Messages.NEW_MESSAGES_ESTIMATED_WITH_DATE",
find: "#{intl::NEW_MESSAGES_ESTIMATED_WITH_DATE}",
replacement: [
{
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/,
@ -505,7 +528,7 @@ export default definePlugin({
},
{
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/,
replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.Messages.DELETED_MESSAGE_COUNT:',
replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.DELETED_MESSAGE_COUNT:',
},
],
predicate: () => Settings.plugins.MessageLogger.collapseDeleted

View file

@ -19,6 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findLazy } from "@webpack";
@ -187,13 +188,13 @@ export default definePlugin({
}
},
{
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,",
find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}",
replacement: [
// make the tag show the right text
{
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.APP_TAG/,
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/,
replace: (_, origSwitch, variant, tags, displayedText, originalText) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}`
},
// show OP tags correctly
{
@ -217,7 +218,7 @@ export default definePlugin({
},
// in the member list
{
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
@ -232,7 +233,7 @@ export default definePlugin({
}
},
{
find: ".Messages.USER_PROFILE_PRONOUNS",
find: "#{intl::USER_PROFILE_PRONOUNS}",
replacement: {
match: /(?=,hideBotTag:!0)/,
replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
@ -295,21 +296,25 @@ export default definePlugin({
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
getTagText(passedTagName: string, strings: Record<string, string>) {
if (!passedTagName) return strings.APP_TAG;
const [tagName, variant] = passedTagName.split("-");
const tag = tags.find(({ name }) => tagName === name);
if (!tag) return strings.APP_TAG;
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return strings.APP_TAG;
getTagText(passedTagName: string, originalText: string) {
try {
const [tagName, variant] = passedTagName.split("-");
if (!passedTagName) return getIntlMessage("APP_TAG");
const tag = tags.find(({ name }) => tagName === name);
if (!tag) return getIntlMessage("APP_TAG");
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG");
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
switch (variant) {
case "OP":
return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER}${tagText}`;
case "BOT":
return `${strings.APP_TAG}${tagText}`;
default:
return tagText;
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
switch (variant) {
case "OP":
return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")}${tagText}`;
case "BOT":
return `${getIntlMessage("APP_TAG")}${tagText}`;
default:
return tagText;
}
} catch {
return originalText;
}
},

View file

@ -18,17 +18,18 @@
import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { i18n } from "@webpack/common";
import { Message } from "discord-types/general";
const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked");
interface MessageDeleteProps {
collapsedReason: {
message: string;
};
// Internal intl message for BLOCKED_MESSAGE_COUNT
collapsedReason: () => any;
}
export default definePlugin({
@ -37,7 +38,7 @@ export default definePlugin({
authors: [Devs.rushii, Devs.Samu],
patches: [
{
find: "Messages.BLOCKED_MESSAGES_HIDE",
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
replacement: [
{
match: /let\{[^}]*collapsedReason[^}]*\}/,
@ -77,6 +78,11 @@ export default definePlugin({
},
shouldHide(props: MessageDeleteProps) {
return !props?.collapsedReason?.message.includes("deleted");
try {
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
} catch (e) {
console.error(e);
}
return false;
}
});

View file

@ -20,7 +20,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "NoMosaic",
authors: [Devs.AutumnVN],
description: "Removes Discord new image mosaic",
description: "Removes Discord image mosaic",
tags: ["image", "mosaic", "media"],
settings,
@ -29,8 +29,8 @@ export default definePlugin({
{
find: '=>"IMAGE"===',
replacement: {
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/,
replace: "=>false;"
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
}
},
{

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.nekohaxx],
patches: [
{
find: "Messages.ONBOARDING_COVER_WELCOME_SUBTITLE",
find: "#{intl::ONBOARDING_COVER_WELCOME_SUBTITLE}",
replacement: {
match: "3e3",
replace: "0"

View file

@ -28,21 +28,21 @@ export default definePlugin({
{
find: '.id,"Search Results"',
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
},
{
find: "renderJumpButton()",
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
},
{
find: "flash:!0,returnMessageId",
replacement: {
match: /.\?(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "false?$1"
}
}

View file

@ -13,7 +13,7 @@ export default definePlugin({
authors: [Devs.bb010g],
patches: [
{
find: ".Messages.COPY_MESSAGE_LINK,",
find: "#{intl::COPY_MESSAGE_LINK}",
replacement: {
match: /\.concat\(location\.host\)/,
replace: ".concat($self.normalizeHost(location.host))",

View file

@ -87,7 +87,7 @@ export default definePlugin({
{
find: "trackAnnouncementMessageLinkClicked({",
replacement: {
match: /function (\i\(\i,\i\)\{)(?=.{0,100}trusted:)/,
match: /function (\i\(\i,\i\)\{)(?=.{0,150}trusted:)/,
replace: "async function $1 if(await $self.handleLink(...arguments)) return;"
}
},

View file

@ -18,8 +18,9 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin from "@utils/types";
import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common";
import { Constants, GuildStore, RestAPI } from "@webpack/common";
function showDisableInvites(guildId: string) {
// @ts-ignore
@ -43,15 +44,15 @@ export default definePlugin({
patches: [
{
find: "Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION",
find: "#{intl::GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION}",
group: true,
replacement: [
{
match: /children:\i\.\i\.\i\.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION/,
match: /children:\i\.\i\.string\(\i\.\i#{intl::GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION}\)/,
replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})",
},
{
match: /\.INVITES_DISABLED\)(?=.+?\.Messages\.INVITES_PERMANENTLY_DISABLED_TIP.+?checked:(\i)).+?\[\1,(\i)\]=\i.useState\(\i\)/,
match: /\.INVITES_DISABLED\)(?=.+?#{intl::INVITES_PERMANENTLY_DISABLED_TIP}.+?checked:(\i)).+?\[\1,(\i)\]=\i.useState\(\i\)/,
replace: "$&,setChecked=$2"
}
]
@ -61,7 +62,7 @@ export default definePlugin({
renderInvitesLabel: ErrorBoundary.wrap(({ guildId, setChecked }) => {
return (
<div>
{i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION}
{getIntlMessage("GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION")}
{showDisableInvites(guildId) && <a role="button" onClick={() => {
setChecked(true);
disableInvites(guildId);

View file

@ -32,7 +32,7 @@ export default definePlugin({
patches: [
// Permission lockout, just set the check to true
{
find: ".STAGE_CHANNEL_CANNOT_OVERWRITE_PERMISSION",
find: "#{intl::STAGE_CHANNEL_CANNOT_OVERWRITE_PERMISSION}",
replacement: [
{
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
@ -43,7 +43,7 @@ export default definePlugin({
},
// Onboarding, same thing but we need to prevent the check
{
find: ".ONBOARDING_CHANNEL_THRESHOLD_WARNING",
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [
{
match: /{(\i:function\(\){return \i},?){2}}/,

View file

@ -19,10 +19,10 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
import { getUniqueUsername } from "@utils/discord";
import { getIntlMessage, getUniqueUsername } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { findByCodeLazy } from "@webpack";
import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import { UnicodeEmoji } from "@webpack/types";
import type { Guild, Role, User } from "discord-types/general";
@ -216,7 +216,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
>
<Menu.MenuItem
id={cl("copy-role-id")}
label={i18n.Messages.COPY_ID_ROLE}
label={getIntlMessage("COPY_ID_ROLE")}
action={() => {
Clipboard.copy(roleId);
}}
@ -225,7 +225,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
{(settings.store as any).unsafeViewAsRole && (
<Menu.MenuItem
id={cl("view-as-role")}
label={i18n.Messages.VIEW_AS_ROLE}
label={getIntlMessage("VIEW_AS_ROLE")}
action={() => {
const role = GuildStore.getRole(guild.id, roleId);
if (!role) return;
@ -257,7 +257,7 @@ function UserContextMenu({ userId }: { userId: string; }) {
>
<Menu.MenuItem
id={cl("copy-user-id")}
label={i18n.Messages.COPY_ID_USER}
label={getIntlMessage("COPY_ID_USER")}
action={() => {
Clipboard.copy(userId);
}}

View file

@ -18,9 +18,10 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { ExpandableHeader } from "@components/ExpandableHeader";
import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common";
import { PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common";
import type { Guild, GuildMember } from "discord-types/general";
import { PermissionsSortOrder, settings } from "..";
@ -105,7 +106,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
permissions: Object.values(PermissionsBits).reduce((prev, curr) => prev | curr, 0n)
});
const OWNER = i18n.Messages.GUILD_OWNER || "Server Owner";
const OWNER = getIntlMessage("GUILD_OWNER") || "Server Owner";
userPermissions.push({
permission: OWNER,
roleName: "Owner",

View file

@ -170,7 +170,7 @@ export default definePlugin({
patches: [
{
find: ".VIEW_ALL_ROLES,",
find: "#{intl::VIEW_ALL_ROLES}",
replacement: {
match: /\.expandButton,.+?null,/,
replace: "$&$self.ViewPermissionsButton(arguments[0]),"

View file

@ -17,8 +17,9 @@
*/
import { classNameFactory } from "@api/Styles";
import { getIntlMessage } from "@utils/discord";
import { wordsToTitle } from "@utils/text";
import { GuildStore, i18n, Parser } from "@webpack/common";
import { GuildStore, Parser } from "@webpack/common";
import { Guild, GuildMember, Role } from "discord-types/general";
import type { ReactNode } from "react";
@ -44,7 +45,7 @@ const PermissionKeyMap = {
export function getPermissionString(permission: string) {
permission = PermissionKeyMap[permission] || permission;
return i18n.Messages[permission] ||
return getIntlMessage(permission) ||
// shouldn't get here but just in case
formatPermissionWithoutMatchingString(permission);
}
@ -58,7 +59,7 @@ export function getPermissionDescription(permission: string): ReactNode {
else if (permission !== "STREAM")
permission = PermissionKeyMap[permission] || permission;
const msg = i18n.Messages[`ROLE_PERMISSIONS_${permission}_DESCRIPTION`] as any;
const msg = getIntlMessage(`ROLE_PERMISSIONS_${permission}_DESCRIPTION`) as any;
if (msg?.hasMarkdown)
return Parser.parse(msg.message);

View file

@ -30,10 +30,10 @@ export default definePlugin({
{
find: ".removeMosaicItemHoverButton),",
replacement: {
match: /\.nonMediaMosaicItem\]:!(\i).{0,50}?children:\[\S,(\S)/,
replace: "$&,$1&&$2&&$self.renderPiPButton(),"
},
},
match: /\.nonMediaMosaicItem\]:.{0,40}children:\[(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,
replace: "$&$1&&$2&&$self.renderPiPButton(),"
}
}
],
renderPiPButton: ErrorBoundary.wrap(() => {

View file

@ -30,7 +30,7 @@ interface ColorPickerWithSwatchesProps {
renderCustomButton?: () => React.ReactNode;
}
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker");
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\("?.+?"?\).+?\])\).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);

View file

@ -50,6 +50,8 @@ async function runMigrations() {
export async function syncAndRunChecks() {
await runMigrations();
if (UserStore.getCurrentUser() == null) return;
const [oldGuilds, oldGroups, oldFriends] = await DataStore.getMany([
guildsKey(),
groupsKey(),

View file

@ -67,7 +67,7 @@ export default definePlugin({
patches: [
{
find: ".REPLY_QUOTE_MESSAGE_BLOCKED",
find: "#{intl::REPLY_QUOTE_MESSAGE_BLOCKED}",
replacement: {
match: /\.onClickReply,.+?}\),(?=\i,\i,\i\])/,
replace: "$&$self.ReplyTimestamp(arguments[0]),"

View file

@ -108,7 +108,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL,shouldHideMediaOptions",
find: "#{intl::MESSAGE_ACTIONS_MENU_LABEL}",
replacement: {
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
replace: (m, target) => `${m}reverseImageSearchType:${target}.getAttribute("data-role"),`

View file

@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";
@ -51,6 +52,12 @@ const settings = definePluginSettings({
description: "Show role colors in the reactors list",
restartNeeded: true
},
pollResults: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in the poll results",
restartNeeded: true
},
colorChatMessages: {
type: OptionType.BOOLEAN,
default: false,
@ -62,14 +69,15 @@ const settings = definePluginSettings({
description: "Intensity of message coloring.",
markers: makeRange(0, 100, 10),
default: 30
},
}
});
export default definePlugin({
name: "RoleColorEverywhere",
authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN, Devs.Kyuuhachi],
authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN, Devs.Kyuuhachi, Devs.jamesbt365],
description: "Adds the top role color anywhere possible",
settings,
patches: [
// Chat Mentions
{
@ -77,82 +85,133 @@ export default definePlugin({
replacement: [
{
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,
replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})"
replace: "$&,color:$self.getColorInt($1?.id,$2?.id)"
}
],
predicate: () => settings.store.chatMentions,
predicate: () => settings.store.chatMentions
},
// Slate
{
find: ".userTooltip,children",
replacement: [
{
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getUserColor($1,{guildId:$2}),"
match: /let\{id:(\i),guildId:\i,channelId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getColorInt($1,$2),"
}
],
predicate: () => settings.store.chatMentions,
predicate: () => settings.store.chatMentions
},
// Member List Role Headers
{
find: 'tutorialId:"whos-online',
replacement: [
{
match: /null,\i," — ",\i\]/,
replace: "null,$self.roleGroupColor(arguments[0])]"
replace: "null,$self.RoleGroupColor(arguments[0])]"
},
],
predicate: () => settings.store.memberList,
predicate: () => settings.store.memberList
},
{
find: ".Messages.THREAD_BROWSER_PRIVATE",
find: "#{intl::THREAD_BROWSER_PRIVATE}",
replacement: [
{
match: /children:\[\i," — ",\i\]/,
replace: "children:[$self.roleGroupColor(arguments[0])]"
replace: "children:[$self.RoleGroupColor(arguments[0])]"
},
],
predicate: () => settings.store.memberList,
predicate: () => settings.store.memberList
},
// Voice Users
{
find: "renderPrioritySpeaker",
find: "renderPrioritySpeaker(){",
replacement: [
{
match: /renderName\(\){.+?usernameSpeaking\]:.+?(?=children)/,
replace: "$&...$self.getVoiceProps(this.props),"
replace: "$&style:$self.getColorStyle(this?.props?.user?.id,this?.props?.guildId),"
}
],
predicate: () => settings.store.voiceUsers,
predicate: () => settings.store.voiceUsers
},
// Reaction List
{
find: ".reactorDefault",
replacement: {
match: /,onContextMenu:e=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/,
replace: "$&,style:{color:$self.getColor($2?.id,$1)}"
match: /,onContextMenu:\i=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/,
replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)"
},
predicate: () => settings.store.reactorsList,
},
// Poll Results
{
find: '.Messages.MESSAGE_EDITED,")"',
find: ",reactionVoteCounts",
replacement: {
match: /\.nickname,(?=children:)/,
replace: "$&style:$self.getColorStyle(arguments[0]?.user?.id,arguments[0]?.channel?.id),"
},
predicate: () => settings.store.pollResults
},
// Messages
{
find: "#{intl::MESSAGE_EDITED}",
replacement: {
match: /(?<=isUnsupported\]:(\i)\.isUnsupported\}\),)(?=children:\[)/,
replace: "style:{color:$self.useMessageColor($1)},"
replace: "style:$self.useMessageColorStyle($1),"
},
predicate: () => settings.store.colorChatMessages,
},
predicate: () => settings.store.colorChatMessages
}
],
settings,
getColor(userId: string, { channelId, guildId }: { channelId?: string; guildId?: string; }) {
if (!(guildId ??= ChannelStore.getChannel(channelId!)?.guild_id)) return null;
return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
getColorString(userId: string, channelOrGuildId: string) {
try {
const guildId = ChannelStore.getChannel(channelOrGuildId)?.guild_id ?? GuildStore.getGuild(channelOrGuildId)?.id;
if (guildId == null) return null;
return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
} catch (e) {
new Logger("RoleColorEverywhere").error("Failed to get color string", e);
}
return null;
},
getUserColor(userId: string, ids: { channelId?: string; guildId?: string; }) {
const colorString = this.getColor(userId, ids);
getColorInt(userId: string, channelOrGuildId: string) {
const colorString = this.getColorString(userId, channelOrGuildId);
return colorString && parseInt(colorString.slice(1), 16);
},
roleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => {
getColorStyle(userId: string, channelOrGuildId: string) {
const colorString = this.getColorString(userId, channelOrGuildId);
return colorString && {
color: colorString
};
},
useMessageColor(message: any) {
try {
const { messageSaturation } = settings.use(["messageSaturation"]);
const author = useMessageAuthor(message);
if (author.colorString != null && messageSaturation !== 0) {
return `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var(--text-normal))`;
}
} catch (e) {
new Logger("RoleColorEverywhere").error("Failed to get message color", e);
}
return null;
},
useMessageColorStyle(message: any) {
const color = this.useMessageColor(message);
return color && {
color
};
},
RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => {
const role = GuildStore.getRole(guildId, id);
return (
@ -164,25 +223,5 @@ export default definePlugin({
{title ?? label} &mdash; {count}
</span>
);
}, { noop: true }),
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
return {
style: {
color: this.getColor(userId, { guildId })
}
};
},
useMessageColor(message: any) {
try {
const { messageSaturation } = settings.use(["messageSaturation"]);
const author = useMessageAuthor(message);
if (author.colorString !== undefined && messageSaturation !== 0)
return `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var(--text-normal))`;
} catch (e) {
console.error("[RCE] failed to get message color", e);
}
return undefined;
},
}, { noop: true })
});

View file

@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
* Copyright (c) 2024 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,9 +18,9 @@
import { IShikiTheme } from "@vap/shiki";
export const SHIKI_REPO = "shikijs/shiki";
export const SHIKI_REPO_COMMIT = "0b28ad8ccfbf2615f2d9d38ea8255416b8ac3043";
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/shiki/themes/${name}.json`;
export const SHIKI_REPO = "shikijs/textmate-grammars-themes";
export const SHIKI_REPO_COMMIT = "2d87559c7601a928b9f7e0f0dda243d2fb6d4499";
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`;
export const themes = {
// Default
@ -30,33 +30,59 @@ export const themes = {
MaterialCandy: "https://raw.githubusercontent.com/millsp/material-candy/master/material-candy.json",
// More from Shiki repo
Andromeeda: shikiRepoTheme("andromeeda"),
AuroraX: shikiRepoTheme("aurora-x"),
AyuDark: shikiRepoTheme("ayu-dark"),
CatppuccinLatte: shikiRepoTheme("catppuccin-latte"),
CatppuccinFrappe: shikiRepoTheme("catppuccin-frappe"),
CatppuccinMacchiato: shikiRepoTheme("catppuccin-macchiato"),
CatppuccinMocha: shikiRepoTheme("catppuccin-mocha"),
DraculaSoft: shikiRepoTheme("dracula-soft"),
Dracula: shikiRepoTheme("dracula"),
EverforestDark: shikiRepoTheme("everforest-dark"),
EverforestLight: shikiRepoTheme("everforest-light"),
GithubDarkDefault: shikiRepoTheme("github-dark-default"),
GithubDarkDimmed: shikiRepoTheme("github-dark-dimmed"),
GithubDarkHighContrast: shikiRepoTheme("github-dark-high-contrast"),
GithubDark: shikiRepoTheme("github-dark"),
GithubLightDefault: shikiRepoTheme("github-light-default"),
GithubLightHighContrast: shikiRepoTheme("github-light-high-contrast"),
GithubLight: shikiRepoTheme("github-light"),
Houston: shikiRepoTheme("houston"),
KanagawaDragon: shikiRepoTheme("kanagawa-dragon"),
KanagawaLotus: shikiRepoTheme("kanagawa-lotus"),
KanagawaWave: shikiRepoTheme("kanagawa-wave"),
LaserWave: shikiRepoTheme("laserwave"),
LightPlus: shikiRepoTheme("light-plus"),
MaterialDarker: shikiRepoTheme("material-darker"),
MaterialDefault: shikiRepoTheme("material-default"),
MaterialLighter: shikiRepoTheme("material-lighter"),
MaterialOcean: shikiRepoTheme("material-ocean"),
MaterialPalenight: shikiRepoTheme("material-palenight"),
MaterialDarker: shikiRepoTheme("material-theme-darker"),
MaterialDefault: shikiRepoTheme("material-theme"),
MaterialLighter: shikiRepoTheme("material-theme-lighter"),
MaterialOcean: shikiRepoTheme("material-theme-ocean"),
MaterialPalenight: shikiRepoTheme("material-theme-palenight"),
MinDark: shikiRepoTheme("min-dark"),
MinLight: shikiRepoTheme("min-light"),
Monokai: shikiRepoTheme("monokai"),
NightOwl: shikiRepoTheme("night-owl"),
Nord: shikiRepoTheme("nord"),
OneDarkPro: shikiRepoTheme("one-dark-pro"),
OneLight: shikiRepoTheme("one-light"),
Plastic: shikiRepoTheme("plastic"),
Poimandres: shikiRepoTheme("poimandres"),
Red: shikiRepoTheme("red"),
RosePineDawn: shikiRepoTheme("rose-pine-dawn"),
RosePineMoon: shikiRepoTheme("rose-pine-moon"),
RosePine: shikiRepoTheme("rose-pine"),
SlackDark: shikiRepoTheme("slack-dark"),
SlackOchin: shikiRepoTheme("slack-ochin"),
SnazzyLight: shikiRepoTheme("snazzy-light"),
SolarizedDark: shikiRepoTheme("solarized-dark"),
SolarizedLight: shikiRepoTheme("solarized-light"),
Synthwave84: shikiRepoTheme("synthwave-84"),
TokyoNight: shikiRepoTheme("tokyo-night"),
Vesper: shikiRepoTheme("vesper"),
VitesseBlack: shikiRepoTheme("vitesse-black"),
VitesseDark: shikiRepoTheme("vitesse-dark"),
VitesseLight: shikiRepoTheme("vitesse-light"),
CssVariables: shikiRepoTheme("css-variables"),
};
export const themeCache = new Map<string, IShikiTheme>();

View file

@ -46,7 +46,7 @@ export default definePlugin({
}
},
{
find: ".PREVIEW_NUM_LINES",
find: "#{intl::PREVIEW_NUM_LINES}",
replacement: {
match: /(?<=function \i\((\i)\)\{)(?=let\{text:\i,language:)/,
replace: "return $self.renderHighlighter({lang:$1.language,content:$1.text});"

View file

@ -26,7 +26,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.MESSAGE_UTILITIES_A11Y_LABEL",
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
match: /isExpanded:\i&&(.+?),/,
replace: "isExpanded:$1,"

View file

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getIntlMessage } from "@utils/discord";
import { findComponentByCodeLazy, findLazy } from "@webpack";
import { i18n, useToken } from "@webpack/common";
import { useToken } from "@webpack/common";
const ColorMap = findLazy(m => m.colors?.INTERACTIVE_MUTED?.css);
const VerifiedIconComponent = findComponentByCodeLazy(".CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP");
const VerifiedIconComponent = findComponentByCodeLazy("#{intl::CONNECTIONS_ROLE_OFFICIAL_ICON_TOOLTIP}");
export function VerifiedIcon() {
const color = useToken(ColorMap.colors.INTERACTIVE_MUTED).hex();
@ -31,7 +32,7 @@ export function VerifiedIcon() {
color={color}
forcedIconColor={forcedIconColor}
size={16}
tooltipText={i18n.Messages.CONNECTION_VERIFIED}
tooltipText={getIntlMessage("CONNECTION_VERIFIED")}
/>
);
}

View file

@ -19,7 +19,7 @@
import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { formatDuration } from "@utils/text";
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
import type { Channel } from "discord-types/general";
@ -80,14 +80,8 @@ const enum ChannelFlags {
const ChatScrollClasses = findByPropsLazy("auto", "managedReactiveScroller");
const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent");
const ChannelBeginHeader = findComponentByCodeLazy(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE");
const TagComponent = findComponentLazy(m => {
if (typeof m !== "function") return false;
const code = Function.prototype.toString.call(m);
// Get the component which doesn't include increasedActivity
return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill");
});
const ChannelBeginHeader = findComponentByCodeLazy("#{intl::ROLE_REQUIRED_SINGLE_USER_MESSAGE}");
const TagComponent = findComponentByCodeLazy("#{intl::FORUM_TAG_A11Y_FILTER_BY_TAG}");
const EmojiParser = findByPropsLazy("convertSurrogateToName");
const EmojiUtils = findByPropsLazy("getURL", "getEmojiColors");

View file

@ -103,7 +103,7 @@ export default definePlugin({
replacement: [
{
// Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel
match: /(?<=getBlockedUsersForVoiceChannel\((\i)\.id\);return)/,
match: /(?<=getBlockedUsersForVoiceChannel\((\i)\.id\);return\()/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
},
{
@ -149,7 +149,7 @@ export default definePlugin({
}
},
{
find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY",
find: "#{intl::CHANNEL_TOOLTIP_DIRECTORY}",
predicate: () => settings.store.showMode === ShowMode.LockIcon,
replacement: {
// Lock Icon
@ -274,7 +274,7 @@ export default definePlugin({
}
},
{
find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE",
find: "#{intl::ROLE_REQUIRED_SINGLE_USER_MESSAGE}",
replacement: [
{
// Change the role permission check to CONNECT if the channel is locked
@ -336,7 +336,7 @@ export default definePlugin({
]
},
{
find: ".Messages.CHANNEL_CALL_CURRENT_SPEAKER.format",
find: "#{intl::CHANNEL_CALL_CURRENT_SPEAKER}",
replacement: [
{
// Remove the divider and the open chat button for the HiddenChannelLockScreen
@ -351,7 +351,7 @@ export default definePlugin({
]
},
{
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY_SHELF_FETCH_ERROR",
find: "#{intl::EMBEDDED_ACTIVITIES_DEVELOPER_ACTIVITY_SHELF_FETCH_ERROR}",
replacement: [
{
// Render our HiddenChannelLockScreen component instead of the main voice channel component
@ -401,7 +401,7 @@ export default definePlugin({
]
},
{
find: ".Messages.STAGE_FULL_MODERATOR_TITLE",
find: "#{intl::STAGE_FULL_MODERATOR_TITLE}",
replacement: [
{
// Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen
@ -463,7 +463,7 @@ export default definePlugin({
]
},
{
find: ".Messages.FORM_LABEL_MUTED",
find: "#{intl::FORM_LABEL_MUTED}",
replacement: {
// Make GuildChannelStore.getChannels return hidden channels
match: /(?<=getChannels\(\i)(?=\))/,

View file

@ -68,7 +68,7 @@ export default definePlugin({
},
// fixes a bug where Members page must be loaded to see highest role, why is Discord depending on MemberSafetyStore.getEnhancedMember for something that can be obtained here?
{
find: "Messages.GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL,allowOverflow",
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}",
predicate: () => settings.store.showModView,
replacement: {
match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
@ -107,7 +107,7 @@ export default definePlugin({
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true,
replacement: {
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\)/g,
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0,rejectWithError:!1\}\)/g,
replace: "Promise.resolve({ body: { valid: true } })"
}
}

View file

@ -9,13 +9,16 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common";
import { ChannelStore, GuildMemberStore, Text, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general";
import { FunctionComponent, ReactNode } from "react";
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
const countDownFilter = canonicalizeMatch("#{intl::MAX_AGE_NEVER}");
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(countDownFilter));
const enum DisplayStyle {
Tooltip = "tooltip",
@ -48,9 +51,14 @@ function renderTimeout(message: Message, inline: boolean) {
/>
);
getIntlMessage("GUILD_ENABLE_COMMUNICATION_TIME_REMAINING", {
username: message.author.username,
countdown
});
return inline
? countdown()
: i18n.Messages.GUILD_ENABLE_COMMUNICATION_TIME_REMAINING.format({
: getIntlMessage("GUILD_ENABLE_COMMUNICATION_TIME_REMAINING", {
username: message.author.username,
countdown
});
@ -65,10 +73,10 @@ export default definePlugin({
patches: [
{
find: ".GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY",
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
replacement: [
{
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
}
]

View file

@ -45,7 +45,7 @@ export default definePlugin({
replace: "}).sortBy(row => $self.wrapSort(($1), row)).value()"
}
}, {
find: ".Messages.FRIEND_REQUEST_CANCEL",
find: "#{intl::FRIEND_REQUEST_CANCEL}",
replacement: {
predicate: () => settings.store.showDates,
match: /subText:(\i)(?<=user:(\i).+?)/,

View file

@ -26,7 +26,7 @@ export default definePlugin({
description: "Adds Startup Timings to the Settings menu",
authors: [Devs.Megu],
patches: [{
find: "Messages.ACTIVITY_SETTINGS",
find: "#{intl::ACTIVITY_SETTINGS}",
replacement: {
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
replace: (_, commaOrSemi, settings, elements) => "" +

View file

@ -38,7 +38,7 @@ export default definePlugin({
// add --avatar-url-<resolution> css variable to avatar img elements
// popout profiles
{
find: ".LABEL_WITH_ONLINE_STATUS",
find: "#{intl::LABEL_WITH_ONLINE_STATUS}",
replacement: {
match: /src:null!=\i\?(\i).{1,50}"aria-hidden":!0/,
replace: "$&,style:$self.getAvatarStyles($1)"
@ -55,6 +55,8 @@ export default definePlugin({
],
getAvatarStyles(src: string) {
if (src.startsWith("data:")) return {};
return Object.fromEntries(
[128, 256, 512, 1024, 2048, 4096].map(size => [
`--avatar-url-${size}`,

View file

@ -21,9 +21,10 @@ import "./style.css";
import { definePluginSettings, Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks";
@ -43,7 +44,7 @@ function getDisplayName(guildId: string, userId: string) {
return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username;
}
function TypingIndicator({ channelId }: { channelId: string; }) {
function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: string; }) {
const typingUsers: Record<string, number> = useStateFromStores(
[TypingStore],
() => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }),
@ -56,7 +57,6 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
}
);
const currentChannelId: string = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId());
const guildId = ChannelStore.getChannel(channelId).guild_id;
if (!settings.store.includeMutedChannels) {
const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId);
@ -75,21 +75,21 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
switch (typingUsersArray.length) {
case 0: break;
case 1: {
tooltipText = i18n.Messages.ONE_USER_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]) });
tooltipText = getIntlMessage("ONE_USER_TYPING", { a: getDisplayName(guildId, typingUsersArray[0]) });
break;
}
case 2: {
tooltipText = i18n.Messages.TWO_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) });
tooltipText = getIntlMessage("TWO_USERS_TYPING", { a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) });
break;
}
case 3: {
tooltipText = i18n.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[2]) });
tooltipText = getIntlMessage("THREE_USERS_TYPING", { a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[2]) });
break;
}
default: {
tooltipText = Settings.plugins.TypingTweaks.enabled
? buildSeveralUsers({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), count: typingUsersArray.length - 2 })
: i18n.Messages.SEVERAL_USERS_TYPING;
: getIntlMessage("SEVERAL_USERS_TYPING");
break;
}
}
@ -164,7 +164,7 @@ export default definePlugin({
find: "UNREAD_IMPORTANT:",
replacement: {
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id)"
replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
}
},
// Theads
@ -173,14 +173,14 @@ export default definePlugin({
find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z",
replacement: {
match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id)"
replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
}
}
],
TypingIndicator: (channelId: string) => (
TypingIndicator: (channelId: string, guildId: string) => (
<ErrorBoundary noop>
<TypingIndicator channelId={channelId} />
<TypingIndicator channelId={channelId} guildId={guildId} />
</ErrorBoundary>
),
});

View file

@ -112,8 +112,8 @@ export default definePlugin({
{
find: "getCooldownTextStyle",
replacement: {
match: /(?<=(\i)\.length\?\i.\i\.Messages.THREE_USERS_TYPING\.format\({\i:(\i),(?:\i:)?(\i),\i:\i}\):)\i\.\i\.Messages\.SEVERAL_USERS_TYPING/,
replace: (_, users, a, b) => `$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`
match: /(,{a:(\i),b:(\i),c:\i}\):)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/,
replace: (_, rest, a, b, users) => `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`
},
predicate: () => settings.store.alternativeFormatting
}
@ -129,14 +129,22 @@ export default definePlugin({
buildSeveralUsers,
mutateChildren(props: any, users: User[], children: any) {
if (!Array.isArray(children)) return children;
try {
if (!Array.isArray(children)) {
return children;
}
let element = 0;
let element = 0;
return children.map(c =>
c.type === "strong"
? <TypingUser {...props} user={users[element++]} />
: c
);
return children.map(c =>
c.type === "strong" || (typeof c !== "string" && !React.isValidElement(c))
? <TypingUser {...props} user={users[element++]} />
: c
);
} catch (e) {
console.error(e);
}
return children;
}
});

View file

@ -25,7 +25,7 @@ export default definePlugin({
settings,
patches: [
{
find: ".Messages.AVATAR_UPLOAD_EDIT_MEDIA",
find: "#{intl::AVATAR_UPLOAD_EDIT_MEDIA}",
replacement: {
match: /maxValue:\d/,
replace: "maxValue:$self.settings.store.zoomMultiplier",

View file

@ -18,9 +18,10 @@
import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary";
import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack";
import { i18n, Tooltip, UserStore } from "@webpack/common";
import { Tooltip, UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
import { settings } from "./settings";
@ -44,7 +45,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
const pronouns = useFormattedPronouns(message.author.id);
return pronouns && (
<Tooltip text={i18n.Messages.USER_PROFILE_PRONOUNS}>
<Tooltip text={getIntlMessage("USER_PROFILE_PRONOUNS")}>
{tooltipProps => (
<span
{...tooltipProps}

View file

@ -14,7 +14,7 @@ import { Channel } from "discord-types/general";
const cl = classNameFactory("vc-uvs-");
const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChannel");
const { useChannelName } = mapMangledModuleLazy(".Messages.GROUP_DM_ALONE", {
const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
useChannelName: filters.byCode("()=>null==")
});
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");

View file

@ -57,7 +57,7 @@ export default definePlugin({
patches: [
// User Popout, Full Size Profile, Direct Messages Side Profile
{
find: ".Messages.USER_PROFILE_LOAD_ERROR",
find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: {
match: /(\.fetchError.+?\?)null/,
replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})`
@ -78,7 +78,7 @@ export default definePlugin({
{
find: "PrivateChannel.renderAvatar",
replacement: {
match: /\.Messages\.CLOSE_DM.+?}\)(?=])/,
match: /#{intl::CLOSE_DM}.+?}\)(?=])/,
replace: "$&,$self.VoiceChannelIndicator({userId:arguments[0]?.user?.id})"
},
predicate: () => settings.store.showVoiceChannelIndicator

View file

@ -37,9 +37,9 @@ export default definePlugin({
authors: [Devs.newwares],
patches: [
{
find: "Messages.REPLY_QUOTE_MESSAGE_NOT_LOADED",
find: "#{intl::REPLY_QUOTE_MESSAGE_NOT_LOADED}",
replacement: {
match: /Messages\.REPLY_QUOTE_MESSAGE_NOT_LOADED/,
match: /#{intl::REPLY_QUOTE_MESSAGE_NOT_LOADED}\)/,
replace: "$&,onMouseEnter:()=>$self.fetchReply(arguments[0])"
}
},

View file

@ -209,10 +209,11 @@ export default definePlugin({
},
// Group DMs top small & large icon
{
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
find: '["aria-hidden"],"aria-label":',
replacement: {
match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
replace: (m, iconUrl) => `${m},onClick:()=>$self.openAvatar(${iconUrl})`
// We have to check that icon is not an unread GDM in the server bar
replace: (m, iconUrl) => `${m},onClick:()=>arguments[0]?.size!=="SIZE_48"&&$self.openAvatar(${iconUrl})`
}
},
// User DMs top small icon

View file

@ -23,11 +23,12 @@ import { CodeBlock } from "@components/CodeBlock";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore, Forms, i18n, Menu, Text } from "@webpack/common";
import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
import { Message } from "discord-types/general";
@ -122,7 +123,7 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu
return (children, props) => {
const value = props[name.toLowerCase()];
if (!value) return;
if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
if (props.label === getIntlMessage("CHANNEL_ACTIONS_MENU_LABEL")) return; // random shit like notification settings
const lastChild = children.at(-1);
if (lastChild?.key === "developer-actions") {
@ -154,6 +155,7 @@ export default definePlugin({
"guild-context": MakeContextCallback("Guild"),
"channel-context": MakeContextCallback("Channel"),
"thread-context": MakeContextCallback("Channel"),
"gdm-context": MakeContextCallback("Channel"),
"user-context": MakeContextCallback("User")
},

View file

@ -57,7 +57,7 @@ export default definePlugin({
patches: [
// Change the max volume for sliders to allow for values above 200
...[
".Messages.USER_VOLUME",
"#{intl::USER_VOLUME}",
"currentVolume:"
].map(find => ({
find,

View file

@ -127,11 +127,11 @@ export default definePlugin({
replace: "return [true"
},
{
match: /(?<=COPY_IMAGE_MENU_ITEM,)action:/,
match: /(?<=#{intl::COPY_IMAGE_MENU_ITEM}\),)action:/,
replace: "action:()=>$self.copyImage(arguments[0]),oldAction:"
},
{
match: /(?<=SAVE_IMAGE_MENU_ITEM,)action:/,
match: /(?<=#{intl::SAVE_IMAGE_MENU_ITEM}\),)action:/,
replace: "action:()=>$self.saveImage(arguments[0]),oldAction:"
},
]
@ -202,14 +202,14 @@ export default definePlugin({
}
},
{
find: ".Messages.SEARCH_WITH_GOOGLE",
find: "#{intl::SEARCH_WITH_GOOGLE}",
replacement: {
match: /\i\.isPlatformEmbedded/,
replace: "true"
}
},
{
find: ".Messages.COPY,hint:",
find: "#{intl::COPY}),hint:",
replacement: [
{
match: /\i\.isPlatformEmbedded/,
@ -224,10 +224,10 @@ export default definePlugin({
{
find: '("interactionUsernameProfile',
replacement:
{
match: /\i\.isPlatformEmbedded(?=.{0,50}\.tagName)/,
replace: "true"
},
{
match: /\i\.isPlatformEmbedded(?=.{0,50}\.tagName)/,
replace: "true"
},
}
],

View file

@ -520,8 +520,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({
id: 721717126523781240n,
},
nyx: {
name: "verticalsync",
id: 328165170536775680n
name: "verticalsync.",
id: 1207087393929171095n
},
nekohaxx: {
name: "nekohaxx",
@ -579,10 +579,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "RamziAH",
id: 1279957227612147747n,
},
SomeAspy: {
SomeAspy: {
name: "SomeAspy",
id: 516750892372852754n,
},
jamesbt365: {
name: "jamesbt365",
id: 158567567487795200n,
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly

View file

@ -19,12 +19,39 @@
import "./discord.css";
import { MessageObject } from "@api/MessageEvents";
import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Channel, Guild, Message, User } from "discord-types/general";
import { Except } from "type-fest";
import { runtimeHashMessageKey } from "./intlHash";
import { Logger } from "./Logger";
import { MediaModalItem, MediaModalProps, openMediaModal } from "./modal";
const IntlManagerLogger = new Logger("IntlManager");
/**
* Get an internationalized message from a non hashed key
* @param key The plain message key
* @param values The values to interpolate, if it's a rich message
*/
export function getIntlMessage(key: string, values?: Record<PropertyKey, any>): any {
return getIntlMessageFromHash(runtimeHashMessageKey(key), values, key);
}
/**
* Get an internationalized message from a hashed key
* @param hashedKey The hashed message key
* @param values The values to interpolate, if it's a rich message
*/
export function getIntlMessageFromHash(hashedKey: string, values?: Record<PropertyKey, any>, originalKey?: string): any {
try {
return values == null ? i18n.intl.string(i18n.t[hashedKey]) : i18n.intl.format(i18n.t[hashedKey], values);
} catch (e) {
IntlManagerLogger.error(`Failed to get intl message for key: ${originalKey ?? hashedKey}`, e);
return originalKey ?? "";
}
}
/**
* Open the invite modal
* @param code The invite code
@ -185,3 +212,14 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
export function getUniqueUsername(user: User) {
return user.discriminator === "0" ? user.username : user.tag;
}
/**
* Get the URL for an emoji. This function always returns a gif URL for animated emojis, instead of webp
* @param id The emoji id
* @param animated Whether the emoji is animated
* @param size The size for the emoji
*/
export function getEmojiURL(id: string, animated: boolean, size: number) {
const url = IconUtils.getEmojiURL({ id, animated, size });
return animated ? url.replace(".webp", ".gif") : url;
}

View file

@ -22,6 +22,7 @@ export * from "./ChangeList";
export * from "./constants";
export * from "./discord";
export * from "./guards";
export * from "./intlHash";
export * from "./lazy";
export * from "./lazyReact";
export * from "./localStorage";

52
src/utils/intlHash.ts Normal file
View file

@ -0,0 +1,52 @@
/* eslint-disable simple-header/header */
/**
* discord-intl
*
* @copyright 2024 Discord, Inc.
* @link https://github.com/discord/discord-intl
* @license MIT
*/
import { hash as h64 } from "@intrnl/xxhash64";
const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");
const IS_BIG_ENDIAN = (() => {
const array = new Uint8Array(4);
const view = new Uint32Array(array.buffer);
return !((view[0] = 1) & array[0]);
})();
function numberToBytes(number: number | bigint) {
number = BigInt(number);
const array: number[] = [];
const byteCount = Math.ceil(Math.floor(Math.log2(Number(number)) + 1) / 8);
for (let i = 0; i < byteCount; i++) {
array.unshift(Number((number >> BigInt(8 * i)) & BigInt(255)));
}
const bytes = new Uint8Array(array);
// The native `hashToMessageKey` always works in Big/Network Endian bytes, so this array
// needs to be converted to the same endianness to get the same base64 result.
return IS_BIG_ENDIAN ? bytes : bytes.reverse();
}
/**
* Returns a consistent, short hash of the given key by first processing it through a hash digest,
* then encoding the first few bytes to base64.
*
* This function is specifically written to mirror the native backend hashing function used by
* `@discord/intl-loader-core`, to be able to hash names at runtime.
*/
export function runtimeHashMessageKey(key: string): string {
const hash = h64(key, 0);
const bytes = numberToBytes(hash);
return [
BASE64_TABLE[bytes[0] >> 2],
BASE64_TABLE[((bytes[0] & 0x03) << 4) | (bytes[1] >> 4)],
BASE64_TABLE[((bytes[1] & 0x0f) << 2) | (bytes[2] >> 6)],
BASE64_TABLE[bytes[2] & 0x3f],
BASE64_TABLE[bytes[3] >> 2],
BASE64_TABLE[((bytes[3] & 0x03) << 4) | (bytes[3] >> 4)],
].join("");
}

View file

@ -16,12 +16,31 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { runtimeHashMessageKey } from "./intlHash";
import { Patch, PatchReplacement, ReplaceFn } from "./types";
export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
if (typeof match === "string") return match;
const canonSource = match.source
.replaceAll("\\i", "[A-Za-z_$][\\w$]*");
let partialCanon = typeof match === "string" ? match : match.source;
partialCanon = partialCanon.replaceAll(/#{intl::([\w$+/]*)(?:::(\w+))?}/g, (_, key, modifier) => {
const hashed = modifier === "raw" ? key : runtimeHashMessageKey(key);
const isString = typeof match === "string";
const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/");
if (hasSpecialChars) {
return isString
? `["${hashed}"]`
: String.raw`(?:\["${hashed}"\])`.replaceAll("+", "\\+");
}
return isString ? `.${hashed}` : String.raw`(?:\.${hashed})`;
});
if (typeof match === "string") {
return partialCanon as T;
}
const canonSource = partialCanon.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`);
return new RegExp(canonSource, match.flags) as T;
}

Some files were not shown because too many files have changed in this diff Show more