Merge branch 'dev' into feat/usercss
This commit is contained in:
commit
828a882017
22 changed files with 406 additions and 82 deletions
12
README.md
12
README.md
|
@ -4,7 +4,9 @@
|
|||
|
||||
The cutest Discord client mod
|
||||
|
||||
![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334)
|
||||
| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) |
|
||||
|:--:|
|
||||
| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) |
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -28,6 +30,14 @@ Visit https://vencord.dev/download
|
|||
|
||||
https://discord.gg/D9uwnFnqmd
|
||||
|
||||
## Sponsors
|
||||
|
||||
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|
||||
|:--:|
|
||||
| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) |
|
||||
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
|
||||
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#Vendicated/Vencord&Timeline">
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
"typescript": "^5.0.4",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.1",
|
||||
"packageManager": "pnpm@8.10.2",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
||||
|
|
|
@ -32,6 +32,8 @@ function isNewer($new: string, old: string) {
|
|||
}
|
||||
|
||||
function patchLatest() {
|
||||
if (process.env.DISABLE_UPDATER_AUTO_PATCHING) return;
|
||||
|
||||
try {
|
||||
const currentAppPath = dirname(process.execPath);
|
||||
const currentVersion = basename(currentAppPath);
|
||||
|
|
24
src/plugins/clientTheme/clientTheme.css
Normal file
24
src/plugins/clientTheme/clientTheme.css
Normal file
|
@ -0,0 +1,24 @@
|
|||
.client-theme-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.client-theme-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.client-theme-settings-labels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.client-theme-container > [class^="colorSwatch"] > [class^="swatch"] {
|
||||
border: thin solid var(--background-modifier-accent) !important;
|
||||
}
|
||||
|
||||
.client-theme-warning {
|
||||
color: var(--text-danger);
|
||||
}
|
228
src/plugins/clientTheme/index.tsx
Normal file
228
src/plugins/clientTheme/index.tsx
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./clientTheme.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { Button, Forms } from "@webpack/common";
|
||||
|
||||
const ColorPicker = findByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR");
|
||||
|
||||
const colorPresets = [
|
||||
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
||||
"#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42",
|
||||
"#3C2E42", "#422938"
|
||||
];
|
||||
|
||||
function onPickColor(color: number) {
|
||||
let hexColor = color.toString(16);
|
||||
|
||||
while (hexColor.length < 6) {
|
||||
hexColor = "0" + hexColor;
|
||||
}
|
||||
|
||||
settings.store.color = hexColor;
|
||||
updateColorVars(hexColor);
|
||||
}
|
||||
|
||||
function ThemeSettings() {
|
||||
const lightnessWarning = hexToLightness(settings.store.color) > 45;
|
||||
const lightModeWarning = getTheme() === Theme.Light;
|
||||
|
||||
return (
|
||||
<div className="client-theme-settings">
|
||||
<div className="client-theme-container">
|
||||
<div className="client-theme-settings-labels">
|
||||
<Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle>
|
||||
<Forms.FormText>Add a color to your Discord client theme</Forms.FormText>
|
||||
</div>
|
||||
<ColorPicker
|
||||
color={parseInt(settings.store.color, 16)}
|
||||
onChange={onPickColor}
|
||||
showEyeDropper={false}
|
||||
suggestedColors={colorPresets}
|
||||
/>
|
||||
</div>
|
||||
{lightnessWarning || lightModeWarning
|
||||
? <div>
|
||||
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
|
||||
<Forms.FormText className="client-theme-warning">Your theme won't look good:</Forms.FormText>
|
||||
{lightnessWarning && <Forms.FormText className="client-theme-warning">Selected color is very light</Forms.FormText>}
|
||||
{lightModeWarning && <Forms.FormText className="client-theme-warning">Light mode isn't supported</Forms.FormText>}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
color: {
|
||||
description: "Color your Discord client theme will be based around. Light mode isn't supported",
|
||||
type: OptionType.COMPONENT,
|
||||
default: "313338",
|
||||
component: () => <ThemeSettings />
|
||||
},
|
||||
resetColor: {
|
||||
description: "Reset Theme Color",
|
||||
type: OptionType.COMPONENT,
|
||||
default: "313338",
|
||||
component: () => (
|
||||
<Button onClick={() => onPickColor(0x313338)}>
|
||||
Reset Theme Color
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ClientTheme",
|
||||
authors: [Devs.F53],
|
||||
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "Could not find app-mount",
|
||||
replacement: {
|
||||
match: /(?<=Could not find app-mount"\))/,
|
||||
replace: ",$self.addThemeInitializer()"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
addThemeInitializer() {
|
||||
document.addEventListener("DOMContentLoaded", this.themeInitializer = () => {
|
||||
updateColorVars(settings.store.color);
|
||||
generateColorOffsets();
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
document.removeEventListener("DOMContentLoaded", this.themeInitializer);
|
||||
document.getElementById("clientThemeVars")?.remove();
|
||||
document.getElementById("clientThemeOffsets")?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
async function generateColorOffsets() {
|
||||
const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g;
|
||||
|
||||
const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
|
||||
const variableLightness = {} as Record<string, number>;
|
||||
|
||||
// Search all stylesheets for color variables
|
||||
for (const styleLinkNode of styleLinkNodes) {
|
||||
const cssLink = styleLinkNode.getAttribute("href");
|
||||
if (!cssLink) continue;
|
||||
|
||||
const res = await fetch(cssLink);
|
||||
const cssString = await res.text();
|
||||
|
||||
// Get lightness values of --primary variables >=500
|
||||
let variableMatch = variableRegex.exec(cssString);
|
||||
while (variableMatch !== null) {
|
||||
const [, variable, lightness] = variableMatch;
|
||||
variableLightness[variable] = parseFloat(lightness);
|
||||
variableMatch = variableRegex.exec(cssString);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate offsets
|
||||
const lightnessOffsets = Object.entries(variableLightness)
|
||||
.map(([key, lightness]) => {
|
||||
const lightnessOffset = lightness - variableLightness["--primary-600-hsl"];
|
||||
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
|
||||
return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.setAttribute("id", "clientThemeOffsets");
|
||||
style.textContent = `:root:root {
|
||||
${lightnessOffsets}
|
||||
}`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function updateColorVars(color: string) {
|
||||
const { hue, saturation, lightness } = hexToHSL(color);
|
||||
|
||||
let style = document.getElementById("clientThemeVars");
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.setAttribute("id", "clientThemeVars");
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
style.textContent = `:root {
|
||||
--theme-h: ${hue};
|
||||
--theme-s: ${saturation}%;
|
||||
--theme-l: ${lightness}%;
|
||||
}`;
|
||||
}
|
||||
|
||||
// https://css-tricks.com/converting-color-spaces-in-javascript/
|
||||
function hexToHSL(hexCode: string) {
|
||||
// Hex => RGB normalized to 0-1
|
||||
const r = parseInt(hexCode.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hexCode.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hexCode.substring(4, 6), 16) / 255;
|
||||
|
||||
// RGB => HSL
|
||||
const cMax = Math.max(r, g, b);
|
||||
const cMin = Math.min(r, g, b);
|
||||
const delta = cMax - cMin;
|
||||
|
||||
let hue: number, saturation: number, lightness: number;
|
||||
|
||||
lightness = (cMax + cMin) / 2;
|
||||
|
||||
if (delta === 0) {
|
||||
// If r=g=b then the only thing that matters is lightness
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
} else {
|
||||
// Magic
|
||||
saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
||||
|
||||
if (cMax === r)
|
||||
hue = ((g - b) / delta) % 6;
|
||||
else if (cMax === g)
|
||||
hue = (b - r) / delta + 2;
|
||||
else
|
||||
hue = (r - g) / delta + 4;
|
||||
hue *= 60;
|
||||
if (hue < 0)
|
||||
hue += 360;
|
||||
}
|
||||
|
||||
// Move saturation and lightness from 0-1 to 0-100
|
||||
saturation *= 100;
|
||||
lightness *= 100;
|
||||
|
||||
return { hue, saturation, lightness };
|
||||
}
|
||||
|
||||
// Minimized math just for lightness, lowers lag when changing colors
|
||||
function hexToLightness(hexCode) {
|
||||
// Hex => RGB normalized to 0-1
|
||||
const r = parseInt(hexCode.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hexCode.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hexCode.substring(4, 6), 16) / 255;
|
||||
|
||||
const cMax = Math.max(r, g, b);
|
||||
const cMin = Math.min(r, g, b);
|
||||
|
||||
const lightness = 100 * ((cMax + cMin) / 2);
|
||||
|
||||
return lightness;
|
||||
}
|
|
@ -50,7 +50,7 @@ async function embedDidMount(this: Component<Props>) {
|
|||
const { titles, thumbnails } = await res.json();
|
||||
|
||||
const hasTitle = titles[0]?.votes >= 0;
|
||||
const hasThumb = thumbnails[0]?.votes >= 0;
|
||||
const hasThumb = thumbnails[0]?.votes >= 0 && !thumbnails[0].original;
|
||||
|
||||
if (!hasTitle && !hasThumb) return;
|
||||
|
||||
|
@ -58,12 +58,12 @@ async function embedDidMount(this: Component<Props>) {
|
|||
enabled: true
|
||||
};
|
||||
|
||||
if (titles[0]?.votes >= 0) {
|
||||
if (hasTitle) {
|
||||
embed.dearrow.oldTitle = embed.rawTitle;
|
||||
embed.rawTitle = titles[0].title;
|
||||
}
|
||||
|
||||
if (thumbnails[0]?.votes >= 0 && thumbnails[0].timestamp) {
|
||||
if (hasThumb) {
|
||||
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
||||
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
|
||||
}
|
||||
|
|
|
@ -77,15 +77,6 @@ export default definePlugin({
|
|||
}
|
||||
]
|
||||
},
|
||||
// Fix search history being disabled / broken with isStaff
|
||||
{
|
||||
find: '("showNewSearch")',
|
||||
predicate: () => settings.store.enableIsStaff,
|
||||
replacement: {
|
||||
match: /(?<=showNewSearch"\);return)\s?/,
|
||||
replace: "!1&&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'H1,title:"Experiments"',
|
||||
replacement: {
|
||||
|
|
|
@ -8,7 +8,6 @@ import * as DataStore from "@api/DataStore";
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { StatusSettingsStores, Tooltip } from "webpack/common";
|
||||
|
@ -27,14 +26,12 @@ interface IgnoredActivity {
|
|||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||
|
||||
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||
const forceUpdate = useForceUpdater();
|
||||
|
||||
return (
|
||||
<Tooltip text={tooltipText}>
|
||||
{tooltipProps => (
|
||||
<button
|
||||
{...tooltipProps}
|
||||
onClick={e => handleActivityToggle(e, activity, forceUpdate)}
|
||||
onClick={e => handleActivityToggle(e, activity)}
|
||||
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
|
||||
>
|
||||
<svg
|
||||
|
@ -54,11 +51,14 @@ const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(act
|
|||
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
|
||||
|
||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||
if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||
const s = settings.use(["ignoredActivities"]);
|
||||
const { ignoredActivities = [] } = s;
|
||||
|
||||
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||
}
|
||||
|
||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => void) {
|
||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
||||
e.stopPropagation();
|
||||
|
||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
||||
|
@ -67,7 +67,6 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|||
|
||||
// Trigger activities recalculation
|
||||
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
||||
forceUpdateButton();
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||
|
@ -90,8 +89,8 @@ export default definePlugin({
|
|||
find: '.displayName="LocalActivityStore"',
|
||||
replacement: [
|
||||
{
|
||||
match: /LISTENING.+?}\),(?<=(\i)\.push.+?)/,
|
||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored),`
|
||||
match: /HANG_STATUS.+?(?=!\i\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
|
||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -302,6 +302,7 @@ export default definePlugin({
|
|||
match: /attachments:(\i)\((\i)\)/,
|
||||
replace:
|
||||
"attachments: $1((() => {" +
|
||||
" if ($self.shouldIgnore($2)) return $2;" +
|
||||
" let old = arguments[1]?.attachments;" +
|
||||
" if (!old) return $2;" +
|
||||
" let new_ = $2.attachments?.map(a => a.id) ?? [];" +
|
||||
|
|
|
@ -25,10 +25,6 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
const EMOTE = "<:luna:1035316192220553236>";
|
||||
const DATA_KEY = "MessageTags_TAGS";
|
||||
const MessageTagsMarker = Symbol("MessageTags");
|
||||
const author = {
|
||||
id: "821472922140803112",
|
||||
bot: false
|
||||
};
|
||||
|
||||
interface Tag {
|
||||
name: string;
|
||||
|
@ -59,14 +55,12 @@ function createTagCommand(tag: Tag) {
|
|||
execute: async (_, ctx) => {
|
||||
if (!await getTag(tag.name)) {
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
||||
});
|
||||
return { content: `/${tag.name}` };
|
||||
}
|
||||
|
||||
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
||||
});
|
||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
||||
|
@ -162,7 +156,6 @@ export default definePlugin({
|
|||
|
||||
if (await getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
||||
});
|
||||
|
||||
|
@ -176,7 +169,6 @@ export default definePlugin({
|
|||
await addTag(tag);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} Successfully created the tag **${name}**!`
|
||||
});
|
||||
break; // end 'create'
|
||||
|
@ -186,7 +178,6 @@ export default definePlugin({
|
|||
|
||||
if (!await getTag(name))
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||
});
|
||||
|
||||
|
@ -194,14 +185,12 @@ export default definePlugin({
|
|||
await removeTag(name);
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
||||
});
|
||||
break; // end 'delete'
|
||||
}
|
||||
case "list": {
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
embeds: [
|
||||
{
|
||||
// @ts-ignore
|
||||
|
@ -224,12 +213,10 @@ export default definePlugin({
|
|||
|
||||
if (!tag)
|
||||
return sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||
});
|
||||
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: tag.message.replaceAll("\\n", "\n")
|
||||
});
|
||||
break; // end 'preview'
|
||||
|
@ -237,7 +224,6 @@ export default definePlugin({
|
|||
|
||||
default: {
|
||||
sendBotMessage(ctx.channel.id, {
|
||||
author,
|
||||
content: "Invalid sub-command"
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -26,15 +26,13 @@ export default definePlugin({
|
|||
authors: [Devs.Ven, Devs.adryd],
|
||||
|
||||
start() {
|
||||
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/5977144dce83e4d71af1de005d16e38eebeb7b72/oneko.js")
|
||||
fetch("https://raw.githubusercontent.com/adryd325/oneko.js/8fa8a1864aa71cd7a794d58bc139e755e96a236c/oneko.js")
|
||||
.then(x => x.text())
|
||||
.then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif"))
|
||||
.then(eval);
|
||||
},
|
||||
|
||||
stop() {
|
||||
clearInterval(window.onekoInterval);
|
||||
delete window.onekoInterval;
|
||||
document.getElementById("oneko")?.remove();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ const Classes = proxyLazy(() => {
|
|||
return Object.assign({}, ...modules);
|
||||
}) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
|
||||
|
||||
function UserPermissionsComponent({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) {
|
||||
function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) {
|
||||
const stns = settings.use(["permissionsSortOrder"]);
|
||||
|
||||
const [rolePermissions, userPermissions] = useMemo(() => {
|
||||
|
@ -76,7 +76,7 @@ function UserPermissionsComponent({ guild, guildMember }: { guild: Guild; guildM
|
|||
sortUserRoles(userRoles);
|
||||
|
||||
for (const [permission, bit] of Object.entries(PermissionsBits)) {
|
||||
for (const { permissions, colorString, position, name } of userRoles) {
|
||||
for (const { permissions, colorString, position } of userRoles) {
|
||||
if ((permissions & bit) === bit) {
|
||||
userPermissions.push({
|
||||
permission: getPermissionString(permission),
|
||||
|
@ -133,7 +133,7 @@ function UserPermissionsComponent({ guild, guildMember }: { guild: Guild; guildM
|
|||
{userPermissions.length > 0 && (
|
||||
<div className={classes(root, roles)}>
|
||||
{userPermissions.map(({ permission, roleColor }) => (
|
||||
<div className={classes(role, rolePill, rolePillBorder)}>
|
||||
<div className={classes(role, rolePill, showBorder ? rolePillBorder : null)}>
|
||||
<div className={roleRemoveButton}>
|
||||
<span
|
||||
className={roleCircle}
|
||||
|
|
|
@ -163,13 +163,13 @@ export default definePlugin({
|
|||
{
|
||||
find: ".popularApplicationCommandIds,",
|
||||
replacement: {
|
||||
match: /showBorder:.{0,60}}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
||||
replace: (m, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember}),`
|
||||
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
||||
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
UserPermissions: (guild: Guild, guildMember?: GuildMember) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} />,
|
||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
||||
|
||||
userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||
channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
|
||||
|
|
|
@ -100,10 +100,10 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
// Fix getRowHeight's check for whether this is the DMs section
|
||||
// section === DMS
|
||||
match: /===\i\.DMS&&0/,
|
||||
// section -1 === DMS
|
||||
replace: "-1$&"
|
||||
// DMS (inlined) === section
|
||||
match: /(?<=getRowHeight=\(.{2,50}?)1===\i/,
|
||||
// DMS (inlined) === section - 1
|
||||
replace: "$&-1"
|
||||
},
|
||||
{
|
||||
// Override scrollToChannel to properly account for pinned channels
|
||||
|
|
|
@ -67,7 +67,7 @@ export default definePlugin({
|
|||
createHighlighter,
|
||||
renderHighlighter: ({ lang, content }: { lang: string; content: string; }) => {
|
||||
return createHighlighter({
|
||||
lang,
|
||||
lang: lang?.toLowerCase(),
|
||||
content,
|
||||
isPreview: false,
|
||||
});
|
||||
|
|
|
@ -68,7 +68,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
// RenderLevel defines if a channel is hidden, collapsed in category, visible, etc
|
||||
find: ".CannotShow=",
|
||||
find: '"placeholder-channel-id"',
|
||||
replacement: [
|
||||
// Remove the special logic for channels we don't have access to
|
||||
{
|
||||
|
@ -77,18 +77,13 @@ export default definePlugin({
|
|||
},
|
||||
// Do not check for unreads when selecting the render level if the channel is hidden
|
||||
{
|
||||
match: /(?=!1===\i.\i\.hasRelevantUnread\(this\.record\))/,
|
||||
match: /(?=!\(0,\i\.getHasImportantUnread\)\(this\.record\))/,
|
||||
replace: "$self.isHiddenChannel(this.record)||"
|
||||
},
|
||||
// Make channels we dont have access to be the same level as normal ones
|
||||
{
|
||||
match: /(?<=renderLevel:(\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/,
|
||||
replace: (_, renderLevelExpression) => renderLevelExpression
|
||||
},
|
||||
// Make channels we dont have access to be the same level as normal ones
|
||||
{
|
||||
match: /(?<=activeJoinedRelevantThreads.+?renderLevel:.+?,threadIds:\i\(this.record.+?renderLevel:)(\i)\..+?(?=,)/,
|
||||
replace: (_, RenderLevels) => `${RenderLevels}.Show`
|
||||
match: /(activeJoinedRelevantThreads:.{0,50}VIEW_CHANNEL.+?renderLevel:(.+?),threadIds.+?renderLevel:).+?(?=,threadIds)/g,
|
||||
replace: (_, rest, defaultRenderLevel) => `${rest}${defaultRenderLevel}`
|
||||
},
|
||||
// Remove permission checking for getRenderLevel function
|
||||
{
|
||||
|
@ -157,7 +152,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: ".UNREAD_HIGHLIGHT",
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
predicate: () => settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
|
||||
replacement: [
|
||||
// Make the channel appear as muted if it's hidden
|
||||
|
@ -178,7 +173,7 @@ export default definePlugin({
|
|||
]
|
||||
},
|
||||
{
|
||||
find: ".UNREAD_HIGHLIGHT",
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
replacement: [
|
||||
{
|
||||
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
|
||||
|
@ -198,7 +193,7 @@ export default definePlugin({
|
|||
// Hide the new version of unreads box for hidden channels
|
||||
find: '.displayName="ChannelListUnreadsStore"',
|
||||
replacement: {
|
||||
match: /(?<=if\(null==(\i))(?=.{0,160}?hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module
|
||||
match: /(?<=if\(null==(\i))(?=.{0,160}?getHasImportantUnread\)\(\i\))/g, // Global because Discord has multiple methods like that in the same module
|
||||
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
|
||||
}
|
||||
},
|
||||
|
@ -206,7 +201,7 @@ export default definePlugin({
|
|||
// Make the old version of unreads box not visible for hidden channels
|
||||
find: "renderBottomUnread(){",
|
||||
replacement: {
|
||||
match: /(?=&&\i\.\i\.hasRelevantUnread\((\i\.record)\))/,
|
||||
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i\.record)\))/,
|
||||
replace: "&&!$self.isHiddenChannel($1)"
|
||||
}
|
||||
},
|
||||
|
@ -214,7 +209,7 @@ export default definePlugin({
|
|||
// Make the state of the old version of unreads box not include hidden channels
|
||||
find: ".useFlattenedChannelIdListWithThreads)",
|
||||
replacement: {
|
||||
match: /(?=&&\i\.\i\.hasRelevantUnread\((\i)\))/,
|
||||
match: /(?=&&\(0,\i\.getHasImportantUnread\)\((\i)\))/,
|
||||
replace: "&&!$self.isHiddenChannel($1)"
|
||||
}
|
||||
},
|
||||
|
@ -260,7 +255,7 @@ export default definePlugin({
|
|||
{
|
||||
find: '"alt+shift+down"',
|
||||
replacement: {
|
||||
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?hasRelevantUnread\(\i\))/,
|
||||
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?getHasImportantUnread\)\(\i\))/,
|
||||
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
|
||||
}
|
||||
},
|
||||
|
|
11
src/plugins/superReactionTweaks/README.md
Normal file
11
src/plugins/superReactionTweaks/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Super Reaction Tweaks
|
||||
|
||||
This plugin applies configurable various tweaks to super reactions.
|
||||
|
||||
![Screenshot](https://user-images.githubusercontent.com/22851444/281598795-58f07116-9f95-4f64-940b-23a5499f2302.png)
|
||||
|
||||
## Features:
|
||||
|
||||
**Super React By Default** - The reaction picker will default to super reactions instead of normal reactions.
|
||||
|
||||
**Super Reaction Play Limit** - Allows you to decide how many super reaction animations can play at once, including removing the limit entirely.
|
63
src/plugins/superReactionTweaks/index.ts
Normal file
63
src/plugins/superReactionTweaks/index.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated, ant0n, FieryFlames and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
superReactByDefault: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Reaction picker will default to Super Reactions",
|
||||
default: true,
|
||||
},
|
||||
unlimitedSuperReactionPlaying: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Remove the limit on Super Reactions playing at once",
|
||||
default: false,
|
||||
},
|
||||
|
||||
superReactionPlayingLimit: {
|
||||
description: "Max Super Reactions to play at once",
|
||||
type: OptionType.SLIDER,
|
||||
default: 20,
|
||||
markers: [5, 10, 20, 40, 60, 80, 100],
|
||||
stickToMarkers: true,
|
||||
},
|
||||
}, {
|
||||
superReactionPlayingLimit: {
|
||||
disabled() { return this.store.unlimitedSuperReactionPlaying; },
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "SuperReactionTweaks",
|
||||
description: "Customize the limit of Super Reactions playing at once, and super react by default",
|
||||
authors: [Devs.FieryFlames, Devs.ant0n],
|
||||
patches: [
|
||||
{
|
||||
find: ",BURST_REACTION_EFFECT_PLAY",
|
||||
replacement: {
|
||||
match: /(?<=BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/,
|
||||
replace: "!$self.shouldPlayBurstReaction($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".hasAvailableBurstCurrency)",
|
||||
replacement: {
|
||||
match: /(?<=\.useBurstReactionsExperiment.{0,20})useState\(!1\)(?=.+?(\i===\i\.EmojiIntention.REACTION))/,
|
||||
replace: "useState($self.settings.store.superReactByDefault && $1)"
|
||||
}
|
||||
}
|
||||
],
|
||||
settings,
|
||||
|
||||
shouldPlayBurstReaction(playingCount: number) {
|
||||
if (settings.store.unlimitedSuperReactionPlaying) return true;
|
||||
if (playingCount <= settings.store.superReactionPlayingLimit) return true;
|
||||
return false;
|
||||
}
|
||||
});
|
|
@ -21,8 +21,8 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { find, findLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildMemberStore, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { find, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildMemberStore, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { buildSeveralUsers } from "../typingTweaks";
|
||||
|
||||
|
@ -36,10 +36,9 @@ const ThreeDots = LazyComponent(() => {
|
|||
const TypingStore = findStoreLazy("TypingStore");
|
||||
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
|
||||
|
||||
const Formatters = findLazy(m => m.Messages?.SEVERAL_USERS_TYPING);
|
||||
|
||||
function getDisplayName(guildId: string, userId: string) {
|
||||
return GuildMemberStore.getNick(guildId, userId) ?? UserStore.getUser(userId).username;
|
||||
const user = UserStore.getUser(userId);
|
||||
return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username;
|
||||
}
|
||||
|
||||
function TypingIndicator({ channelId }: { channelId: string; }) {
|
||||
|
@ -51,7 +50,7 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
|
|||
const oldKeys = Object.keys(old);
|
||||
const currentKeys = Object.keys(current);
|
||||
|
||||
return oldKeys.length === currentKeys.length && JSON.stringify(oldKeys) === JSON.stringify(currentKeys);
|
||||
return oldKeys.length === currentKeys.length && currentKeys.every(key => old[key] != null);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -70,21 +69,21 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
|
|||
switch (typingUsersArray.length) {
|
||||
case 0: break;
|
||||
case 1: {
|
||||
tooltipText = Formatters.Messages.ONE_USER_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]) });
|
||||
tooltipText = i18n.Messages.ONE_USER_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]) });
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
tooltipText = Formatters.Messages.TWO_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) });
|
||||
tooltipText = i18n.Messages.TWO_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) });
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
tooltipText = Formatters.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[2]) });
|
||||
tooltipText = i18n.Messages.THREE_USERS_TYPING.format({ 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 })
|
||||
: Formatters.Messages.SEVERAL_USERS_TYPING;
|
||||
: i18n.Messages.SEVERAL_USERS_TYPING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -92,11 +91,10 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
|
|||
if (typingUsersArray.length > 0) {
|
||||
return (
|
||||
<Tooltip text={tooltipText!}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
{props => (
|
||||
<div
|
||||
{...props}
|
||||
style={{ marginLeft: 6, height: 16, display: "flex", alignItems: "center", zIndex: 0, cursor: "pointer" }}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
>
|
||||
<ThreeDots dotRadius={3} themed={true} />
|
||||
</div>
|
||||
|
@ -128,12 +126,22 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
patches: [
|
||||
// Normal channel
|
||||
{
|
||||
find: ".UNREAD_HIGHLIGHT",
|
||||
find: "UNREAD_IMPORTANT:",
|
||||
replacement: {
|
||||
match: /channel:(\i).{0,100}?channelEmoji,.{0,250}?\.children.{0,50}?:null/,
|
||||
replace: "$&,$self.TypingIndicator($1.id)"
|
||||
}
|
||||
},
|
||||
// Theads
|
||||
{
|
||||
// This is the thread "spine" that shows in the left
|
||||
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)"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -267,6 +267,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "Dziurwa",
|
||||
id: 1001086404203389018n
|
||||
},
|
||||
F53: {
|
||||
name: "F53",
|
||||
id: 280411966126948353n
|
||||
},
|
||||
AutumnVN: {
|
||||
name: "AutumnVN",
|
||||
id: 393694671383166998n
|
||||
|
@ -379,6 +383,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "ProffDea",
|
||||
id: 609329952180928513n
|
||||
},
|
||||
ant0n: {
|
||||
name: "ant0n",
|
||||
id: 145224646868860928n
|
||||
},
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
|
2
src/webpack/common/types/utils.d.ts
vendored
2
src/webpack/common/types/utils.d.ts
vendored
|
@ -159,5 +159,5 @@ export interface i18n {
|
|||
|
||||
loadPromise: Promise<void>;
|
||||
|
||||
Messages: Record<i18nMessages, string>;
|
||||
Messages: Record<i18nMessages, any>;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ waitFor(["ComponentDispatch", "ComponentDispatcher"], m => ComponentDispatch = m
|
|||
export const RestAPI: t.RestAPI = findByPropsLazy("getAPIBaseURL", "get");
|
||||
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
|
||||
|
||||
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight");
|
||||
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage");
|
||||
|
||||
export const lodash: typeof import("lodash") = findByPropsLazy("debounce", "cloneDeep");
|
||||
|
||||
|
|
Loading…
Reference in a new issue