mirror of
https://github.com/Equicord/Equicord.git
synced 2025-03-14 14:10:26 -04: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.
|
- Request for plugins from Discord.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Extra included plugins (120 additional plugins)</summary>
|
<summary>Extra included plugins (122 additional plugins)</summary>
|
||||||
|
|
||||||
- AllCallTimers by MaxHerbold and D3SOX
|
- AllCallTimers by MaxHerbold and D3SOX
|
||||||
- AltKrispSwitch by newwares
|
- AltKrispSwitch by newwares
|
||||||
|
@ -87,6 +87,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
||||||
- LoginWithQR by nexpid
|
- LoginWithQR by nexpid
|
||||||
- MediaDownloader by Colorman
|
- MediaDownloader by Colorman
|
||||||
- Meow by Samwich
|
- Meow by Samwich
|
||||||
|
- MessageColors by hen
|
||||||
- MessageLinkTooltip by Kyuuhachi
|
- MessageLinkTooltip by Kyuuhachi
|
||||||
- MessageLoggerEnhanced by Aria
|
- MessageLoggerEnhanced by Aria
|
||||||
- MessageTranslate by Samwich
|
- MessageTranslate by Samwich
|
||||||
|
@ -96,6 +97,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
||||||
- NoBulletPoints by Samwich
|
- NoBulletPoints by Samwich
|
||||||
- NoDefaultEmojis by Samwich
|
- NoDefaultEmojis by Samwich
|
||||||
- NoDeleteSafety by Samwich
|
- NoDeleteSafety by Samwich
|
||||||
|
- NoMirroredCamera by Nyx
|
||||||
- NoModalAnimation by AutumnVN
|
- NoModalAnimation by AutumnVN
|
||||||
- NoNitroUpsell by thororen
|
- NoNitroUpsell by thororen
|
||||||
- NoRoleHeaders by Samwich
|
- 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
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EquicordDevs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoMirroredCamera",
|
name: "NoMirroredCamera",
|
||||||
description: "Prevents the camera from being mirrored on your screen",
|
description: "Prevents the camera from being mirrored on your screen",
|
||||||
authors: [EquicordDevs.nyx],
|
authors: [Devs.nyx],
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
// When focused on voice channel or group chat voice call
|
// When focused on voice channel or group chat voice call
|
||||||
|
|
|
@ -747,6 +747,10 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "x3rt",
|
name: "x3rt",
|
||||||
id: 131602100332396544n
|
id: 131602100332396544n
|
||||||
},
|
},
|
||||||
|
hen: {
|
||||||
|
id: 279266228151779329n,
|
||||||
|
name: "Hen",
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
Loading…
Add table
Reference in a new issue