mirror of
https://github.com/Equicord/Equicord.git
synced 2025-05-11 18:05:37 +02:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
aca48cb9e5
29 changed files with 227 additions and 197 deletions
|
@ -191,8 +191,8 @@ const buildConfigs = ([
|
||||||
globalName: "Vencord",
|
globalName: "Vencord",
|
||||||
sourcemap,
|
sourcemap,
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("equicordDesktop"),
|
globPlugins("equibop"),
|
||||||
...commonOpts.plugins
|
...commonRendererPlugins
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
|
|
|
@ -132,7 +132,7 @@ export const makeAllPackagesExternalPlugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {(kind: "web" | "discordDesktop" | "vencordDesktop" | "equicordDesktop") => import("esbuild").Plugin}
|
* @type {(kind: "web" | "discordDesktop" | "vesktop" | "equibop") => import("esbuild").Plugin}
|
||||||
*/
|
*/
|
||||||
export const globPlugins = kind => ({
|
export const globPlugins = kind => ({
|
||||||
name: "glob-plugins",
|
name: "glob-plugins",
|
||||||
|
@ -171,8 +171,8 @@ export const globPlugins = kind => ({
|
||||||
(target === "web" && kind === "discordDesktop") ||
|
(target === "web" && kind === "discordDesktop") ||
|
||||||
(target === "desktop" && kind === "web") ||
|
(target === "desktop" && kind === "web") ||
|
||||||
(target === "discordDesktop" && kind !== "discordDesktop") ||
|
(target === "discordDesktop" && kind !== "discordDesktop") ||
|
||||||
(target === "vencordDesktop" && kind !== "vencordDesktop" && kind !== "equicordDesktop") ||
|
(target === "vesktop" && kind !== "vesktop" && kind !== "equibop") ||
|
||||||
(target === "equicordDesktop" && kind !== "equicordDesktop" && kind !== "vencordDesktop");
|
(target === "equibop" && kind !== "equibop" && kind !== "vesktop");
|
||||||
|
|
||||||
if (excluded) {
|
if (excluded) {
|
||||||
const name = await resolvePluginName(fullDir, file);
|
const name = await resolvePluginName(fullDir, file);
|
||||||
|
@ -358,10 +358,6 @@ export const commonOpts = {
|
||||||
jsxFragment: "VencordFragment"
|
jsxFragment: "VencordFragment"
|
||||||
};
|
};
|
||||||
|
|
||||||
const escapedBuiltinModules = builtinModules
|
|
||||||
.map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
|
|
||||||
.join("|");
|
|
||||||
|
|
||||||
export const commonRendererPlugins = [
|
export const commonRendererPlugins = [
|
||||||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
||||||
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
||||||
|
|
|
@ -45,7 +45,7 @@ interface PluginData {
|
||||||
commands: Command[];
|
commands: Command[];
|
||||||
required: boolean;
|
required: boolean;
|
||||||
enabledByDefault: boolean;
|
enabledByDefault: boolean;
|
||||||
target: "discordDesktop" | "vencordDesktop" | "equicordDesktop" | "desktop" | "web" | "dev";
|
target: "discordDesktop" | "vesktop" | "equibop" | "desktop" | "web" | "dev";
|
||||||
filePath: string;
|
filePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ async function parseFile(fileName: string) {
|
||||||
|
|
||||||
const target = getPluginTarget(fileName);
|
const target = getPluginTarget(fileName);
|
||||||
if (target) {
|
if (target) {
|
||||||
if (!["web", "discordDesktop", "vencordDesktop", "equicordDesktop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
|
if (!["web", "discordDesktop", "vesktop", "equibop", "desktop", "dev"].includes(target)) throw fail(`invalid target ${target}`);
|
||||||
data.target = target as any;
|
data.target = target as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visual-refresh .vc-notification-root {
|
||||||
|
background-color: var(--bg-overlay-floating, var(--background-base-low));
|
||||||
|
}
|
||||||
|
|
||||||
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
|
|
|
@ -192,11 +192,11 @@ function ExcludedPluginsList({ search }: { search: string; }) {
|
||||||
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
|
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
|
||||||
.filter(([name]) => name.toLowerCase().includes(search));
|
.filter(([name]) => name.toLowerCase().includes(search));
|
||||||
|
|
||||||
const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "equicordDesktop" | "desktop" | "dev", string> = {
|
const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = {
|
||||||
desktop: "Discord Desktop app or Vesktop",
|
desktop: "Discord Desktop app or Vesktop",
|
||||||
discordDesktop: "Discord Desktop app",
|
discordDesktop: "Discord Desktop app",
|
||||||
vencordDesktop: "Vesktop Equibop apps",
|
vesktop: "Vesktop Equibop apps",
|
||||||
equicordDesktop: "Vesktop & Equibop apps",
|
equibop: "Vesktop & Equibop apps",
|
||||||
web: "Vesktop & Equibop apps as well as the Web version of Discord",
|
web: "Vesktop & Equibop apps as well as the Web version of Discord",
|
||||||
dev: "Developer version of Equicord"
|
dev: "Developer version of Equicord"
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface SwitchProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SWITCH_ON = "var(--green-360)";
|
const SWITCH_ON = "var(--brand-500)";
|
||||||
const SWITCH_OFF = "var(--primary-400)";
|
const SWITCH_OFF = "var(--primary-400)";
|
||||||
const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
const SwitchClasses = findByPropsLazy("slider", "input", "container");
|
||||||
|
|
||||||
|
|
2
src/modules.d.ts
vendored
2
src/modules.d.ts
vendored
|
@ -25,7 +25,7 @@ declare module "~plugins" {
|
||||||
folderName: string;
|
folderName: string;
|
||||||
userPlugin: boolean;
|
userPlugin: boolean;
|
||||||
}>;
|
}>;
|
||||||
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "equicordDesktop" | "desktop" | "dev">;
|
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev">;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "~pluginNatives" {
|
declare module "~pluginNatives" {
|
||||||
|
|
|
@ -81,8 +81,8 @@ export default definePlugin({
|
||||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(\.AVATAR,children:.+?onRequestClose:\(\)=>\{)/,
|
match: /\.AVATAR,children:.+?onRequestClose:\(\)=>\{/,
|
||||||
replace: "$&onRequestClose:$self.onPopoutClose();"
|
replace: "$&$self.onPopoutClose();"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=#{intl::SET_STATUS}\),)/,
|
match: /(?<=#{intl::SET_STATUS}\),)/,
|
||||||
|
|
|
@ -45,8 +45,8 @@ export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
// Also display flex otherwise to fix scrolling
|
// Also display flex otherwise to fix scrolling
|
||||||
const barStyle = {
|
const barStyle = {
|
||||||
display: isFullscreen ? "none" : "flex",
|
display: isFullscreen ? "none" : "flex",
|
||||||
gridArea: "sidebar"
|
gridArea: "betterFoldersSidebar"
|
||||||
} as CSSProperties;
|
} satisfies CSSProperties;
|
||||||
|
|
||||||
if (!guilds || !settings.store.sidebarAnim) {
|
if (!guilds || !settings.store.sidebarAnim) {
|
||||||
return visible
|
return visible
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./sidebarFix.css";
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
|
@ -104,7 +106,7 @@ export const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let cssMade = false;
|
const cssMade = false;
|
||||||
|
|
||||||
const cssElementId = "VC-BetterFolders";
|
const cssElementId = "VC-BetterFolders";
|
||||||
|
|
||||||
|
@ -202,6 +204,13 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
||||||
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
|
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Discord adds a slight bottom margin of 4px when it's expanded
|
||||||
|
// Which looks off when there's nothing open in the folder
|
||||||
|
predicate: () => !settings.store.keepIcons,
|
||||||
|
match: /(?=className:.{0,50}folderIcon)/,
|
||||||
|
replace: "style:arguments[0]?.isBetterFolders?{}:{marginBottom:0},"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -212,12 +221,17 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Render the Better Folders sidebar
|
// Render the Better Folders sidebar
|
||||||
|
// Discord has two different places where they render the sidebar.
|
||||||
|
// One is for visual refresh, one is not,
|
||||||
|
// and each has a bunch of conditions &&ed in front of it.
|
||||||
|
// Add the betterFolders sidebar to both, keeping the conditions Discord uses.
|
||||||
match: /(?<=[[,])((?:!?\i&&)+)\(.{0,50}({className:\i\.guilds,themeOverride:\i})\)/g,
|
match: /(?<=[[,])((?:!?\i&&)+)\(.{0,50}({className:\i\.guilds,themeOverride:\i})\)/g,
|
||||||
replace: (_, conditions, props) => `${_},${conditions}$self.FolderSideBar({...${props}})`
|
replace: (m, conditions, props) => `${m},${conditions}$self.FolderSideBar(${props})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// Add grid styles to fix aligment with other visual refresh elements
|
||||||
match: /(?<=className:)(\i\.base)(?=,)/,
|
match: /(?<=className:)(\i\.base)(?=,)/,
|
||||||
replace: "($self.makePatchedBaseCSS($1))"
|
replace: "`${$self.gridStyle} ${$1}`"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -276,46 +290,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
gridStyle: "vc-BetterFolders-sidebar-grid",
|
gridStyle: "vc-betterFolders-sidebar-grid",
|
||||||
makePatchedBaseCSS(className: string) {
|
|
||||||
done: try {
|
|
||||||
if (cssMade) break done;
|
|
||||||
const rule = [...document.styleSheets]
|
|
||||||
.flatMap(x => [...x.cssRules])
|
|
||||||
// cant do includes because they have a `not ((grid-template-columns`
|
|
||||||
// dumb type inference
|
|
||||||
.filter((x): x is CSSSupportsRule => x instanceof CSSSupportsRule && x.conditionText.startsWith("(grid-template-columns"))
|
|
||||||
.flatMap(x => [...x.cssRules])
|
|
||||||
.filter(x => x instanceof CSSStyleRule)
|
|
||||||
.find(x => x.selectorText.endsWith(`.${className}`));
|
|
||||||
if (!rule) {
|
|
||||||
console.error("Failed to find css rule for betterFolders");
|
|
||||||
break done;
|
|
||||||
}
|
|
||||||
const areas = rule.style.gridTemplateAreas
|
|
||||||
.split('" "')
|
|
||||||
.map(x => x.replace(/"/g, "").split(" "));
|
|
||||||
areas[0].splice(1, 0, areas[0][0]);
|
|
||||||
areas[1].splice(1, 0, "sidebar");
|
|
||||||
areas[2].splice(1, 0, "sidebar");
|
|
||||||
const css = `
|
|
||||||
.visual-refresh .${this.gridStyle} {
|
|
||||||
grid-template-areas: ${areas.map(x => `"${x.join(" ")}"`).join(" ")};
|
|
||||||
grid-template-columns: ${rule.style.gridTemplateColumns.replace(/(?<=guildsEnd\])/, " min-content [sidebarEnd]")};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
const element = document.createElement("style");
|
|
||||||
element.id = cssElementId;
|
|
||||||
element.textContent = css;
|
|
||||||
document.getElementById(cssElementId)?.remove();
|
|
||||||
document.head.appendChild(element);
|
|
||||||
cssMade = true;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return className;
|
|
||||||
}
|
|
||||||
return `${className} ${this.gridStyle}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set<any>) {
|
getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set<any>) {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
|
9
src/plugins/betterFolders/sidebarFix.css
Normal file
9
src/plugins/betterFolders/sidebarFix.css
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */
|
||||||
|
|
||||||
|
.visual-refresh .vc-betterFolders-sidebar-grid {
|
||||||
|
grid-template-columns: [start] min-content [guildsEnd] min-content [sidebarEnd] min-content [channelsEnd] 1fr [end]; /* stylelint-disable-line value-keyword-case */
|
||||||
|
grid-template-areas:
|
||||||
|
"titleBar titleBar titleBar titleBar"
|
||||||
|
"guildsList betterFoldersSidebar notice notice"
|
||||||
|
"guildsList betterFoldersSidebar channelsList page";
|
||||||
|
}
|
|
@ -130,12 +130,15 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const variableRegex = /(--neutral-\d{1,3}-hsl):.*?(\S*)%;/g;
|
const visualRefreshVariableRegex = /(--neutral-\d{1,3}-hsl):.*?(\S*)%;/g;
|
||||||
|
const oldVariableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g;
|
||||||
|
const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g;
|
||||||
|
const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g;
|
||||||
|
|
||||||
// generates variables per theme by:
|
// generates variables per theme by:
|
||||||
// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600)
|
// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600)
|
||||||
function genThemeSpecificOffsets(variableLightness: Record<string, number>, centerVariable: string): string {
|
function genThemeSpecificOffsets(variableLightness: Record<string, number>, regex: RegExp | null, centerVariable: string): string {
|
||||||
return Object.entries(variableLightness)
|
return Object.entries(variableLightness).filter(([key]) => regex == null || key.search(regex) > -1)
|
||||||
.map(([key, lightness]) => {
|
.map(([key, lightness]) => {
|
||||||
const lightnessOffset = lightness - variableLightness[centerVariable];
|
const lightnessOffset = lightness - variableLightness[centerVariable];
|
||||||
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
|
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
|
||||||
|
@ -145,23 +148,65 @@ function genThemeSpecificOffsets(variableLightness: Record<string, number>, cent
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateColorOffsets(styles) {
|
function generateColorOffsets(styles) {
|
||||||
const variableLightness = {} as Record<string, number>;
|
const oldVariableLightness = {} as Record<string, number>;
|
||||||
|
const visualRefreshVariableLightness = {} as Record<string, number>;
|
||||||
|
|
||||||
// Get lightness values of --primary variables
|
// Get lightness values of --primary variables
|
||||||
let variableMatch = variableRegex.exec(styles);
|
for (const [, variable, lightness] of styles.matchAll(oldVariableRegex)) {
|
||||||
while (variableMatch !== null) {
|
oldVariableLightness[variable] = parseFloat(lightness);
|
||||||
const [, variable, lightness] = variableMatch;
|
}
|
||||||
variableLightness[variable] = parseFloat(lightness);
|
|
||||||
variableMatch = variableRegex.exec(styles);
|
for (const [, variable, lightness] of styles.matchAll(visualRefreshVariableRegex)) {
|
||||||
|
visualRefreshVariableLightness[variable] = parseFloat(lightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
createStyleSheet("clientThemeOffsets", [
|
createStyleSheet("clientThemeOffsets", [
|
||||||
// you can determine the "center color" by looking at the variable used by `--background-primary`
|
`.theme-light {\n ${genThemeSpecificOffsets(oldVariableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`,
|
||||||
`.theme-light {\n ${genThemeSpecificOffsets(variableLightness, "--neutral-2-hsl")} \n}`,
|
`.theme-dark {\n ${genThemeSpecificOffsets(oldVariableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`,
|
||||||
`.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, "--neutral-69-hsl")} \n}`,
|
`.visual-refresh.theme-light {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-2-hsl")} \n}`,
|
||||||
|
`.visual-refresh.theme-dark {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-69-hsl")} \n}`,
|
||||||
].join("\n\n"));
|
].join("\n\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateLightModeFixes(styles: string) {
|
||||||
|
const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm;
|
||||||
|
// get light capturing groups that mention --white-500
|
||||||
|
const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat();
|
||||||
|
|
||||||
|
const groupBackgroundRegex = /^([^{]*)\{background:var\(--white-500\)/m;
|
||||||
|
const groupBackgroundColorRegex = /^([^{]*)\{background-color:var\(--white-500\)/m;
|
||||||
|
// find all capturing groups that assign background or background-color directly to w500
|
||||||
|
const backgroundGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundRegex)).join(",\n");
|
||||||
|
const backgroundColorGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundColorRegex)).join(",\n");
|
||||||
|
// create css to reassign them to --primary-100
|
||||||
|
const reassignBackgrounds = `${backgroundGroups} {\n background: var(--primary-100) \n}`;
|
||||||
|
const reassignBackgroundColors = `${backgroundColorGroups} {\n background-color: var(--primary-100) \n}`;
|
||||||
|
|
||||||
|
const groupBgVarRegex = /\.theme-light\{([^}]*--[^:}]*(?:background|bg)[^:}]*:var\(--white-500\)[^}]*)\}/m;
|
||||||
|
const bgVarRegex = /^(--[^:]*(?:background|bg)[^:]*):var\(--white-500\)/m;
|
||||||
|
// get all global variables used for backgrounds
|
||||||
|
const lightVars = mapReject(relevantStyles, style => captureOne(style, groupBgVarRegex)) // get the insides of capture groups that have at least one background var with w500
|
||||||
|
.map(str => str.split(";")).flat(); // captureGroupInsides[] -> cssRule[]
|
||||||
|
const lightBgVars = mapReject(lightVars, variable => captureOne(variable, bgVarRegex)); // remove vars that aren't for backgrounds or w500
|
||||||
|
// create css to reassign every var
|
||||||
|
const reassignVariables = `.theme-light {\n ${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")} \n}`;
|
||||||
|
|
||||||
|
createStyleSheet("clientThemeLightModeFixes", [
|
||||||
|
reassignBackgrounds,
|
||||||
|
reassignBackgroundColors,
|
||||||
|
reassignVariables,
|
||||||
|
].join("\n\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureOne(str, regex) {
|
||||||
|
const result = str.match(regex);
|
||||||
|
return (result === null) ? null : result[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapReject(arr, mapFunc) {
|
||||||
|
return arr.map(mapFunc).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function updateColorVars(color: string) {
|
function updateColorVars(color: string) {
|
||||||
const { hue, saturation, lightness } = hexToHSL(color);
|
const { hue, saturation, lightness } = hexToHSL(color);
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,7 @@ export default definePlugin({
|
||||||
this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
|
this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Noop,
|
||||||
NoopLogger: () => NoopLogger,
|
NoopLogger: () => NoopLogger,
|
||||||
|
|
||||||
shouldLog(logger: string, level: keyof AllowLevels) {
|
shouldLog(logger: string, level: keyof AllowLevels) {
|
||||||
|
@ -149,14 +150,14 @@ export default definePlugin({
|
||||||
find: "is not a valid locale.",
|
find: "is not a valid locale.",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i\.error(?=\(""\.concat\(\i," is not a valid locale."\)\))/,
|
match: /\i\.error(?=\(""\.concat\(\i," is not a valid locale."\)\))/,
|
||||||
replace: "$self.NoopLogger"
|
replace: "$self.Noop"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /console\.log(?=\("AppCrashedFatalReport: getLastCrash not supported\."\))/,
|
match: /console\.log(?=\("AppCrashedFatalReport: getLastCrash not supported\."\))/,
|
||||||
replace: "$self.NoopLogger"
|
replace: "$self.Noop"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -155,6 +155,7 @@ function makeShortcuts() {
|
||||||
|
|
||||||
Stores: Webpack.fluxStores,
|
Stores: Webpack.fluxStores,
|
||||||
|
|
||||||
|
// e.g. "2024-05_desktop_visual_refresh", 0
|
||||||
setExperiment: (id: string, bucket: number) => {
|
setExperiment: (id: string, bucket: number) => {
|
||||||
Common.FluxDispatcher.dispatch({
|
Common.FluxDispatcher.dispatch({
|
||||||
type: "EXPERIMENT_OVERRIDE_BUCKET",
|
type: "EXPERIMENT_OVERRIDE_BUCKET",
|
||||||
|
|
|
@ -47,19 +47,11 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
|
match: /\i\.\i\.dispatch\({type:"IDLE",idle:!1}\)/,
|
||||||
replace: "$self.handleOnline()"
|
replace: "$self.handleOnline()"
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /(setInterval\(\i,30\*)\i\.\i\.Millis\.SECOND/,
|
|
||||||
replace: "$1$self.getIntervalDelay()" // For web installs
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
getIntervalDelay() {
|
|
||||||
return Math.min(6e5, this.getIdleTimeout());
|
|
||||||
},
|
|
||||||
|
|
||||||
handleOnline() {
|
handleOnline() {
|
||||||
if (!settings.store.remainInIdle) {
|
if (!settings.store.remainInIdle) {
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
|
|
23
src/plugins/disableDeepLinks.vesktop/index.ts
Normal file
23
src/plugins/disableDeepLinks.vesktop/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "DisableDeepLinks",
|
||||||
|
description: "Disables Discord stupid DeepLinks experiment which makes the app unusable",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
required: true,
|
||||||
|
|
||||||
|
patches: [{
|
||||||
|
find: "2025-03_desktop_deeplinks",
|
||||||
|
replacement: {
|
||||||
|
match: /config:{enabled:!0/,
|
||||||
|
replace: "config:{enabled:!1",
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { ChannelStore, Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common";
|
import { Constants, FluxDispatcher, GuildStore, RelationshipStore, RestAPI, SnowflakeUtils, UserStore } from "@webpack/common";
|
||||||
import { Settings } from "Vencord";
|
import { Settings } from "Vencord";
|
||||||
|
|
||||||
const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore");
|
const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore");
|
||||||
|
@ -121,55 +121,61 @@ export default definePlugin({
|
||||||
: comparator(row);
|
: comparator(row);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async refreshUserAffinities() {
|
||||||
|
try {
|
||||||
|
await RestAPI.get({ url: "/users/@me/affinities/users", retries: 3 }).then(({ body }) => {
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "LOAD_USER_AFFINITIES_SUCCESS",
|
||||||
|
affinities: body,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Not a critical error if this fails for some reason
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async fetchImplicitRelationships() {
|
async fetchImplicitRelationships() {
|
||||||
// Implicit relationships are defined as users that you:
|
// Implicit relationships are defined as users that you:
|
||||||
// 1. Have an affinity for
|
// 1. Have an affinity for
|
||||||
// 2. Do not have a relationship with // TODO: Check how this works with pending/blocked relationships
|
// 2. Do not have a relationship with
|
||||||
// 3. Have a mutual guild with
|
await this.refreshUserAffinities();
|
||||||
const userAffinities: Set<string> = UserAffinitiesStore.getUserAffinitiesUserIds();
|
const userAffinities: Set<string> = UserAffinitiesStore.getUserAffinitiesUserIds();
|
||||||
|
const relationships = RelationshipStore.getRelationships();
|
||||||
const nonFriendAffinities = Array.from(userAffinities).filter(
|
const nonFriendAffinities = Array.from(userAffinities).filter(
|
||||||
id => !RelationshipStore.getRelationshipType(id)
|
id => !RelationshipStore.getRelationshipType(id)
|
||||||
);
|
);
|
||||||
|
nonFriendAffinities.forEach(id => {
|
||||||
|
relationships[id] = 5;
|
||||||
|
});
|
||||||
|
RelationshipStore.emitChange();
|
||||||
|
|
||||||
// I would love to just check user cache here (falling back to the gateway of course)
|
const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id));
|
||||||
// However, users in user cache may just be there because they share a DM or group DM with you
|
|
||||||
// So there's no guarantee that a user being in user cache means they have a mutual with you
|
|
||||||
// To get around this, we request users we have DMs with, and ignore them below if we don't get them back
|
|
||||||
const dmUserIds = new Set(
|
|
||||||
Object.values(ChannelStore.getSortedPrivateChannels()).flatMap(c => c.recipients)
|
|
||||||
);
|
|
||||||
const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id) || dmUserIds.has(id));
|
|
||||||
const allGuildIds = Object.keys(GuildStore.getGuilds());
|
const allGuildIds = Object.keys(GuildStore.getGuilds());
|
||||||
const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());
|
const sentNonce = SnowflakeUtils.fromTimestamp(Date.now());
|
||||||
let count = allGuildIds.length * Math.ceil(toRequest.length / 100);
|
let count = allGuildIds.length * Math.ceil(toRequest.length / 100);
|
||||||
|
|
||||||
// OP 8 Request Guild Members allows 100 user IDs at a time
|
// OP 8 Request Guild Members allows 100 user IDs at a time
|
||||||
const ignore = new Set(toRequest);
|
// Note: As we are using OP 8 here, implicit relationships who we do not share a guild
|
||||||
const relationships = RelationshipStore.getRelationships();
|
// with will not be fetched; so, if they're not otherwise cached, they will not be shown
|
||||||
|
// This should not be a big deal as these should be rare
|
||||||
const callback = ({ chunks }) => {
|
const callback = ({ chunks }) => {
|
||||||
for (const chunk of chunks) {
|
const chunkCount = chunks.filter(chunk => chunk.nonce === sentNonce).length;
|
||||||
const { nonce, members } = chunk;
|
if (chunkCount === 0) return;
|
||||||
if (nonce !== sentNonce) return;
|
|
||||||
members.forEach(member => {
|
|
||||||
ignore.delete(member.user.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
nonFriendAffinities.map(id => UserStore.getUser(id)).filter(user => user && !ignore.has(user.id)).forEach(user => relationships[user.id] = 5);
|
count -= chunkCount;
|
||||||
RelationshipStore.emitChange();
|
RelationshipStore.emitChange();
|
||||||
if (--count === 0) {
|
if (count <= 0) {
|
||||||
// @ts-ignore
|
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||||
FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK_BATCH", callback);
|
||||||
for (let i = 0; i < toRequest.length; i += 100) {
|
for (let i = 0; i < toRequest.length; i += 100) {
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
type: "GUILD_MEMBERS_REQUEST",
|
type: "GUILD_MEMBERS_REQUEST",
|
||||||
guildIds: allGuildIds,
|
guildIds: allGuildIds,
|
||||||
userIds: toRequest.slice(i, i + 100),
|
userIds: toRequest.slice(i, i + 100),
|
||||||
|
presences: true,
|
||||||
nonce: sentNonce,
|
nonce: sentNonce,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,8 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
<svg
|
<svg
|
||||||
aria-hidden
|
aria-hidden
|
||||||
role="img"
|
role="img"
|
||||||
width="24"
|
width="20"
|
||||||
height="24"
|
height="20"
|
||||||
viewBox={"0 0 64 64"}
|
viewBox={"0 0 64 64"}
|
||||||
style={{ scale: "1.39", translate: "0 -1px" }}
|
style={{ scale: "1.39", translate: "0 -1px" }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -67,8 +67,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '="SYSTEM_TAG"',
|
find: '="SYSTEM_TAG"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\i.gradientClassName]\),style:.{0,80}:void 0\}\)\(\),)/,
|
match: /\i.gradientClassName]\),style:/,
|
||||||
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
replace: "$&{color:$self.calculateNameColorForMessageContext(arguments[0])},_style:"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/8fa8a1864aa71cd7a794d58bc139e755e96a236c/oneko.js")
|
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/c4ee66353b11a44e4a5b7e914a81f8d33111555e/oneko.js")
|
||||||
.then(x => x.text())
|
.then(x => x.text())
|
||||||
.then(s => s.replace("const nekoSpeed = 10;", `const nekoSpeed = ${settings.store.speed};`))
|
.then(s => s.replace("const nekoSpeed = 10;", `const nekoSpeed = ${settings.store.speed};`))
|
||||||
.then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif")
|
.then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif")
|
||||||
|
|
|
@ -105,8 +105,8 @@ const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { atta
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
width="24"
|
width="20"
|
||||||
height="24"
|
height="20"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
style={{ scale: "1.096", translate: "0 -1px" }}
|
style={{ scale: "1.096", translate: "0 -1px" }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -144,8 +144,8 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
role="img"
|
role="img"
|
||||||
width="24"
|
width="20"
|
||||||
height="24"
|
height="20"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
style={{ scale: "1.2" }}
|
style={{ scale: "1.2" }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -75,8 +75,8 @@ const SilentMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
onClick={() => setEnabledValue(!enabled)}
|
onClick={() => setEnabledValue(!enabled)}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="20"
|
||||||
height="24"
|
height="20"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
style={{ scale: "1.2" }}
|
style={{ scale: "1.2" }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -96,7 +96,7 @@ const SilentTypingToggle: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
|
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
|
||||||
<path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
|
<path fill="currentColor" mask="url(#silent-typing-msg-mask)" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
|
||||||
{isEnabled && (
|
{isEnabled && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandInputType, Command, findOption, OptionalMessageOption, sendBotMessage } from "@api/Commands";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { sendMessage } from "@utils/discord";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { FluxDispatcher, MessageActions } from "@webpack/common";
|
import { FluxDispatcher, MessageActions } from "@webpack/common";
|
||||||
|
@ -55,21 +56,36 @@ interface Track {
|
||||||
const Spotify = findByPropsLazy("getPlayerState");
|
const Spotify = findByPropsLazy("getPlayerState");
|
||||||
const PendingReplyStore = findByPropsLazy("getPendingReply");
|
const PendingReplyStore = findByPropsLazy("getPendingReply");
|
||||||
|
|
||||||
function sendMessage(channelId, message) {
|
function makeCommand(name: string, formatUrl: (track: Track) => string): Command {
|
||||||
message = {
|
return {
|
||||||
// The following are required to prevent Discord from throwing an error
|
name,
|
||||||
invalidEmojis: [],
|
description: `Share your current Spotify ${name} in chat`,
|
||||||
tts: false,
|
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||||
validNonShortcutEmojis: [],
|
options: [OptionalMessageOption],
|
||||||
...message
|
execute(options, { channel }) {
|
||||||
};
|
const track: Track | null = Spotify.getTrack();
|
||||||
const reply = PendingReplyStore.getPendingReply(channelId);
|
if (!track) {
|
||||||
MessageActions.sendMessage(channelId, message, void 0, MessageActions.getSendMessageOptionsForReply(reply))
|
return sendBotMessage(channel.id, {
|
||||||
.then(() => {
|
content: "You're not listening to any music."
|
||||||
if (reply) {
|
});
|
||||||
FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId });
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
const data = formatUrl(track);
|
||||||
|
const message = findOption(options, "message");
|
||||||
|
|
||||||
|
// Note: Due to how Discord handles commands, we need to manually create and send the message
|
||||||
|
|
||||||
|
sendMessage(
|
||||||
|
channel.id,
|
||||||
|
{ content: message ? `${message} ${data}` : data },
|
||||||
|
false,
|
||||||
|
MessageActions.getSendMessageOptionsForReply(PendingReplyStore.getPendingReply(channel.id))
|
||||||
|
).then(() => {
|
||||||
|
FluxDispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: channel.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -77,60 +93,8 @@ export default definePlugin({
|
||||||
description: "Share your current Spotify track, album or artist via slash command (/track, /album, /artist)",
|
description: "Share your current Spotify track, album or artist via slash command (/track, /album, /artist)",
|
||||||
authors: [Devs.katlyn],
|
authors: [Devs.katlyn],
|
||||||
commands: [
|
commands: [
|
||||||
{
|
makeCommand("track", track => `https://open.spotify.com/track/${track.id}`),
|
||||||
name: "track",
|
makeCommand("album", track => `https://open.spotify.com/album/${track.album.id}`),
|
||||||
description: "Send your current Spotify track to chat",
|
makeCommand("artist", track => track.artists[0].external_urls.spotify)
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [],
|
|
||||||
execute: (_, ctx) => {
|
|
||||||
const track: Track | null = Spotify.getTrack();
|
|
||||||
if (track === null) {
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "You're not listening to any music."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Note: Due to how Discord handles commands, we need to manually create and send the message
|
|
||||||
sendMessage(ctx.channel.id, {
|
|
||||||
content: `https://open.spotify.com/track/${track.id}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "album",
|
|
||||||
description: "Send your current Spotify album to chat",
|
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [],
|
|
||||||
execute: (_, ctx) => {
|
|
||||||
const track: Track | null = Spotify.getTrack();
|
|
||||||
if (track === null) {
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "You're not listening to any music."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage(ctx.channel.id, {
|
|
||||||
content: `https://open.spotify.com/album/${track.album.id}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "artist",
|
|
||||||
description: "Send your current Spotify artist to chat",
|
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [],
|
|
||||||
execute: (_, ctx) => {
|
|
||||||
const track: Track | null = Spotify.getTrack();
|
|
||||||
if (track === null) {
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "You're not listening to any music."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage(ctx.channel.id, {
|
|
||||||
content: track.artists[0].external_urls.spotify
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { settings } from "./settings";
|
||||||
import { TranslateModal } from "./TranslateModal";
|
import { TranslateModal } from "./TranslateModal";
|
||||||
import { cl } from "./utils";
|
import { cl } from "./utils";
|
||||||
|
|
||||||
export function TranslateIcon({ height = 24, width = 24, className }: { height?: number; width?: number; className?: string; }) {
|
export function TranslateIcon({ height = 20, width = 20, className }: { height?: number; width?: number; className?: string; }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 96 960 960"
|
viewBox="0 96 960 960"
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/com
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
import managedStyle from "./styles.css?managed";
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showAvatars: {
|
showAvatars: {
|
||||||
|
@ -75,7 +75,7 @@ const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) {
|
||||||
openUserProfile(user.id);
|
openUserProfile(user.id);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
color: typingUserColor(guildId, user.id),
|
color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings.store.showAvatars && (
|
{settings.store.showAvatars && (
|
||||||
|
@ -97,7 +97,9 @@ export default definePlugin({
|
||||||
description: "Show avatars and role colours in the typing indicator",
|
description: "Show avatars and role colours in the typing indicator",
|
||||||
authors: [Devs.zt],
|
authors: [Devs.zt],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
managedStyle,
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::THREE_USERS_TYPING}",
|
find: "#{intl::THREE_USERS_TYPING}",
|
||||||
|
|
5
src/plugins/typingTweaks/style.css
Normal file
5
src/plugins/typingTweaks/style.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.vc-typing-user [class^="wrapper"] {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
vertical-align: -4px;
|
||||||
|
}
|
|
@ -98,7 +98,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
|
SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
|
||||||
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
||||||
ThemeStore.addChangeListener(initThemes);
|
|
||||||
|
let currentTheme = ThemeStore.theme;
|
||||||
|
ThemeStore.addChangeListener(() => {
|
||||||
|
if (currentTheme === ThemeStore.theme) return;
|
||||||
|
|
||||||
|
currentTheme = ThemeStore.theme;
|
||||||
|
initThemes();
|
||||||
|
});
|
||||||
|
|
||||||
if (!IS_WEB)
|
if (!IS_WEB)
|
||||||
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
VencordNative.quickCss.addThemeChangeListener(initThemes);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue