mirror of
https://github.com/Equicord/Equicord.git
synced 2025-01-18 13:23:28 -05:00
feat(plugin): MessageColors
This commit is contained in:
parent
202de5af0b
commit
59e32da053
6 changed files with 295 additions and 3 deletions
|
@ -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
|
||||
|
|
63
src/equicordplugins/messageColors/constants.ts
Normal file
63
src/equicordplugins/messageColors/constants.ts
Normal 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; });
|
203
src/equicordplugins/messageColors/index.tsx
Normal file
203
src/equicordplugins/messageColors/index.tsx
Normal 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("°", "");
|
||||
}
|
||||
}
|
20
src/equicordplugins/messageColors/styles.css
Normal file
20
src/equicordplugins/messageColors/styles.css
Normal 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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue