feat(plugin): MessageColors

This commit is contained in:
thororen1234 2024-08-08 14:10:46 -04:00
parent 202de5af0b
commit 59e32da053
6 changed files with 295 additions and 3 deletions

View file

@ -21,7 +21,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- Request for plugins from Discord.
<details>
<summary>Extra included plugins (120 additional plugins)</summary>
<summary>Extra included plugins (122 additional plugins)</summary>
- AllCallTimers by MaxHerbold and D3SOX
- AltKrispSwitch by newwares
@ -87,6 +87,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- LoginWithQR by nexpid
- MediaDownloader by Colorman
- Meow by Samwich
- MessageColors by hen
- MessageLinkTooltip by Kyuuhachi
- MessageLoggerEnhanced by Aria
- MessageTranslate by Samwich
@ -96,6 +97,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- NoBulletPoints by Samwich
- NoDefaultEmojis by Samwich
- NoDeleteSafety by Samwich
- NoMirroredCamera by Nyx
- NoModalAnimation by AutumnVN
- NoNitroUpsell by thororen
- NoRoleHeaders by Samwich

View file

@ -0,0 +1,63 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";
export const enum RenderType {
BLOCK,
FOREGROUND,
BACKGROUND,
}
export const settings = definePluginSettings({
renderType: {
type: OptionType.SELECT,
description: "How to render colors",
options: [
{
label: "Text color",
value: RenderType.FOREGROUND,
default: true,
},
{
label: "Block nearby",
value: RenderType.BLOCK,
},
{
label: "Background color",
value: RenderType.BACKGROUND
},
]
}
});
export const enum ColorType {
RGB,
RGBA,
HEX,
HSL
}
// It's sooo hard to read regex without this, it makes it at least somewhat bearable
export const replaceRegexp = (reg: string) => {
const n = new RegExp(reg
// \c - 'comma'
// \v - 'value'
// \f - 'float'
.replaceAll("\\f", "[+-]?([0-9]*[.])?[0-9]+")
.replaceAll("\\c", "(?:,|\\s)")
.replaceAll("\\v", "\\s*?\\d+?\\s*?"), "g");
return n;
};
export const regex = [
{ reg: /rgb\(\v\c\v\c\v\)/g, type: ColorType.RGB },
{ reg: /rgba\(\v\c\v\c\v(\c|\/?)\s*\f\)/g, type: ColorType.RGBA },
{ reg: /hsl\(\v°?\c\s*?\d+%?\s*?\c\s*?\d+%?\s*?\)/g, type: ColorType.HSL },
{ reg: /#(?:[0-9a-fA-F]{3}){1,2}/g, type: ColorType.HEX }
].map(v => { v.reg = replaceRegexp(v.reg.source); return v; });

View file

@ -0,0 +1,203 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { React } from "@webpack/common";
import {
ColorType,
regex,
RenderType,
replaceRegexp,
settings,
} from "./constants";
const source = regex.map(r => r.reg.source).join("|");
const matchAllRegExp = new RegExp(`^(${source})`, "i");
interface ParsedColorInfo {
type: "color";
color: string;
colorType: ColorType;
text: string;
}
const requiredFirstCharacters = ["r", "h", "#"].flatMap(v => [
v,
v.toUpperCase(),
]);
export default definePlugin({
authors: [EquicordDevs.hen],
name: "MessageColors",
description: "Displays color codes like #FF0042 inside of messages",
settings,
patches: [
// Create a new markdown rule, so it parses just like any other features
// Like bolding, spoilers, mentions, etc
{
find: "roleMention:{order:",
group: true,
replacement: {
match: /roleMention:\{order:(\i\.\i\.order)/,
replace: "color:$self.getColor($1),$&",
},
},
// Changes text md rule regex, so it stops right before hsl( | rgb(
// Without it discord will try to pass a string without those to color rule
{
find: ".defaultRules.text,match:",
group: true,
replacement: {
// $)/)
match: /\$\)\/\)}/,
// hsl(|rgb(|$&
replace: requiredFirstCharacters.join("|") + "|$&",
},
},
// Discord just requires it to be here
// Or it explodes (bad)
{
find: "Unknown markdown rule:",
group: true,
replacement: {
match: /roleMention:{type:/,
replace: 'color:{type:"inlineObject"},$&',
},
},
],
getColor(order: number) {
return {
order,
// Don't even try to match if the message chunk doesn't start with...
requiredFirstCharacters,
// Match -> Parse -> React
// Result of previous action is dropped as a first argument of the next one
match(content: string) {
return matchAllRegExp.exec(content);
},
parse(
matchedContent: RegExpExecArray,
_,
parseProps: Record<string, any>,
): ParsedColorInfo | { type: "text"; content: string; } {
// This check makes sure that it doesn't try to parse color
// When typing/editing message
//
// Discord doesn't know how to deal with color and crashes
if (!parseProps.messageId)
return {
type: "text",
content: matchedContent[0],
};
const content = matchedContent[0];
try {
const type = getColorType(content);
return {
type: "color",
colorType: type,
color: parseColor(content, type),
text: content,
};
} catch (e) {
console.error(e);
return {
type: "text",
content: matchedContent[0],
};
}
},
// react(args: ReturnType<typeof this.parse>)
react({ text, colorType, color }: ParsedColorInfo) {
if (settings.store.renderType === RenderType.FOREGROUND) {
return <span style={{ color: color }}>{text}</span>;
}
const styles = {
"--color": color,
} as React.CSSProperties;
if (settings.store.renderType === RenderType.BACKGROUND) {
const isDark = isColorDark(color, colorType);
const className = `vc-color-bg ${!isDark ? "vc-color-bg-invert" : ""}`;
return (
<span className={className} style={styles}>
{text}
</span>
);
}
return (
<>
{text}
<span className="vc-color-block" style={styles}></span>
</>
);
},
};
},
});
// https://en.wikipedia.org/wiki/Relative_luminance
const calcRGBLightness = (r: number, g: number, b: number) => {
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const isColorDark = (color: string, type: ColorType): boolean => {
switch (type) {
case ColorType.RGBA:
case ColorType.RGB: {
const match = color.match(/\d+/g)!;
const lightness = calcRGBLightness(+match[0], +match[1], +match[2]);
return lightness < 140;
}
case ColorType.HEX: {
var rgb = parseInt(color.substring(1), 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
const lightness = calcRGBLightness(r, g, b);
return lightness < 140;
}
case ColorType.HSL: {
const match = color.match(/\d+/g)!;
const lightness = +match[2];
return lightness < 50;
}
}
};
const getColorType = (color: string): ColorType => {
color = color.toLowerCase().trim();
if (color.startsWith("#")) return ColorType.HEX;
if (color.startsWith("hsl")) return ColorType.HSL;
if (color.startsWith("rgba")) return ColorType.RGBA;
if (color.startsWith("rgb")) return ColorType.RGB;
throw new Error(`Can't resolve color type of ${color}`);
};
function parseColor(str: string, type: ColorType): string {
str = str
.toLowerCase()
.trim()
.replaceAll(/(\s|,)+/g, " ");
switch (type) {
case ColorType.RGB:
return str;
case ColorType.RGBA:
if (!str.includes("/"))
return str.replaceAll(replaceRegexp(/\f(?=\s*?\))/.source), "/$&");
return str;
case ColorType.HEX:
return str[0] === "#" ? str : `#${str}`;
case ColorType.HSL:
return str.replace("°", "");
}
}

View file

@ -0,0 +1,20 @@
.vc-color-block {
aspect-ratio: 1/1;
background: var(--color);
height: 1rem;
vertical-align: middle;
border-radius: 4px;
display: inline-block;
user-select: none;
margin-left: 2px;
}
.vc-color-bg {
background: var(--color);
}
/* Light color in dark theme */
.theme-dark .vc-color-bg.vc-color-bg-invert,
.theme-light .vc-color-bg:not(.vc-color-bg-invert) {
color: var(--background-tertiary);
}

View file

@ -4,13 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { EquicordDevs } from "@utils/constants";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoMirroredCamera",
description: "Prevents the camera from being mirrored on your screen",
authors: [EquicordDevs.nyx],
authors: [Devs.nyx],
patches: [
// When focused on voice channel or group chat voice call

View file

@ -747,6 +747,10 @@ export const EquicordDevs = Object.freeze({
name: "x3rt",
id: 131602100332396544n
},
hen: {
id: 279266228151779329n,
name: "Hen",
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly