This commit is contained in:
vishnyanetchereshnya 2024-09-17 22:49:29 +02:00 committed by GitHub
commit 06735fd034
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1223 additions and 0 deletions

View file

@ -0,0 +1,12 @@
# EnhancedUserTags
Replaces and extends default tags (Official, Original Poster, etc.) with a crown icon, the type of which depends on the user's permissions
## Preview
![preview](https://i.imgur.com/HRnjNPB.png)
![preview](https://i.imgur.com/KUP5K9S.png)
![preview](https://i.imgur.com/He6sLru.png)
![preview](https://i.imgur.com/IBlGqg4.png)
![preview](https://i.imgur.com/6ORXZj8.png)
![preview](https://i.imgur.com/qkmmYoi.png)

View file

@ -0,0 +1,67 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { get, set } from "@api/DataStore";
import { createColoredCrownIcon, tagIcons } from "./components/Icons";
import { TAGS } from "./tag";
import { ColoredTag, CSSHex, CustomColoredTag, TagColors } from "./types";
const STORE_KEY = "EnhancedUserTagColors";
const DEFAULT_TAG_COLORS: TagColors = {
[TAGS.THREAD_CREATOR]: "#D9A02D",
[TAGS.POST_CREATOR]: "#D9A02D",
[TAGS.MODERATOR]: "#AA6000",
[TAGS.ADMINISTRATOR]: "#E0E0E0",
[TAGS.GROUP_OWNER]: "#D9A02D",
[TAGS.GUILD_OWNER]: "#D9A02D",
[TAGS.BOT]: "#0BDA51",
[TAGS.WEBHOOK]: "#5865F2",
};
const tagColors: TagColors = new Proxy({ ...DEFAULT_TAG_COLORS }, {
// auto recreate tags on color change
// mb there's some way to re-render component by providing props into React.memo instead recreating it
// but sadly don't have much exp with react
set: (target, tag, color) => {
// no need to recreate component if color have no changes
if (color !== target[tag]) {
target[tag] = color;
tagIcons[tag] = createColoredCrownIcon(tag as any as CustomColoredTag);
}
return true;
},
});
async function initColors() {
const savedColors = await get<Partial<TagColors>>(STORE_KEY);
if (!savedColors) {
await set(STORE_KEY, { ...tagColors });
} else {
Object.assign(tagColors, savedColors);
}
}
initColors();
export const getColor = (tag: ColoredTag): CSSHex => {
return tagColors[tag];
};
export const setColor = async (tag: CustomColoredTag, color: CSSHex) => {
tagColors[tag] = color;
await set(STORE_KEY, { ...tagColors });
};
export const resetColor = async (tag: CustomColoredTag) => {
tagColors[tag] = DEFAULT_TAG_COLORS[tag];
await set(STORE_KEY, { ...tagColors });
};

View file

@ -0,0 +1,139 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findComponentByCodeLazy } from "@webpack";
import { Button, Forms, Text, useMemo, useState } from "@webpack/common";
import { getColor, resetColor, setColor } from "../colors";
import { TAG_NAMES, TAGS } from "../tag";
import { CustomColoredTag } from "../types";
import { hex2number, number2hex } from "../util/hex";
import { ColorlessCrownIcon, HalfedCrownIcon } from "./Icons";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const SUGGESTED_COLORS = [
"#AA6000", "#E0E0E0", "#D9A02D", "#0BDA51", "#5865F2",
"#5865F2", "#C41E3A", "#BF40BF", "#5D3FD3", "#B2BEB5",
];
export default function ColorSettings() {
return (
<div>
<Forms.FormTitle tag="h3">Custom Tags Color</Forms.FormTitle>
<div
style={{
display: "flex",
justifyContent: "space-around",
}}
>
{
([TAGS.MODERATOR, TAGS.ADMINISTRATOR, TAGS.BOT, TAGS.WEBHOOK] as CustomColoredTag[]).map(tag => {
const [curColor, setCurColor] = useState(hex2number(getColor(tag)));
const crownStyle = useMemo<React.CSSProperties>(() => {
return {
display: "flex",
position: "relative",
width: "16px",
height: "16px",
alignSelf: "center",
color: number2hex(curColor)
};
}, [curColor]);
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "4px"
}}
>
<Text
variant="heading-md/normal"
>
{TAG_NAMES[tag]}
</Text>
<div
style={{
display: "inline-flex",
marginBottom: "4px",
gap: "4px",
}}
>
<span
style={crownStyle}
>
<ColorlessCrownIcon />
</span>
{
tag !== TAGS.WEBHOOK ? null : <span
style={crownStyle}
>
<ColorlessCrownIcon />
{
<HalfedCrownIcon />
}
</span>
}
</div>
<div
style={{
display: "flex",
gap: "4px",
}}
>
<ColorPicker
color={curColor}
onChange={setCurColor}
showEyeDropper={false}
SUGGESTED_COLORS={SUGGESTED_COLORS}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
}}
>
<Button
size={Button.Sizes.NONE}
color={Button.Colors.GREEN}
style={{
width: "50px",
height: "100%",
padding: "0 4px",
}}
onClick={() => setColor(tag, number2hex(curColor))}
>
Save
</Button>
<Button
size={Button.Sizes.NONE}
color={Button.Colors.RED}
style={{
width: "50px",
height: "auto",
padding: "0 4px",
}}
onClick={() => {
resetColor(tag);
setCurColor(hex2number(getColor(tag)));
}}
>
Reset
</Button>
</div>
</div>
</div>
);
})
}
</div>
</div>
);
}

View file

@ -0,0 +1,46 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Tooltip } from "@webpack/common";
import { TagDetails } from "../tag";
import { HalfedCrownIcon } from "./Icons";
export default function EnhancedUserTag(tagDetails: TagDetails & {
style: React.CSSProperties;
}) {
// only original discord tags have no text
// and them already have `span` wrapper with styles so no need to wrap into extra one
if (!tagDetails.text)
return <tagDetails.icon />;
// for custom official tag
if (tagDetails.gap)
tagDetails.style.gap = "4px";
return <Tooltip
text={tagDetails.text}
// @ts-ignore
tooltipStyle={{
// to fit largest text of tooltip `Thread Creator | Moderator (Timeout Members,`
maxWidth: "350px",
whiteSpace: "pre-line",
} as React.CSSProperties}
>
{({ onMouseEnter, onMouseLeave }) => (
<span
style={tagDetails.style}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<tagDetails.icon />
{
tagDetails.halfGold ? <HalfedCrownIcon /> : null
}
</span>
)}
</Tooltip>;
}

View file

@ -0,0 +1,144 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { LazyComponent } from "@utils/lazyReact";
import { React, Tooltip } from "@webpack/common";
import { getColor } from "../colors";
import { TAGS } from "../tag";
import { ColoredTag } from "../types";
export const createColoredCrownIcon = (tag: ColoredTag) => LazyComponent(
() => React.memo(
() => (
<svg width="100%" height="100%" viewBox="1.35 2 21 21" fill={getColor(tag)}>
<path d="M5 18a1 1 0 0 0-1 1 3 3 0 0 0 3 3h10a3 3 0 0 0 3-3 1 1 0 0 0-1-1H5ZM3.04 7.76a1 1 0 0 0-1.52 1.15l2.25 6.42a1 1 0 0 0 .94.67h14.55a1 1 0 0 0 .95-.71l1.94-6.45a1 1 0 0 0-1.55-1.1l-4.11 3-3.55-5.33.82-.82a.83.83 0 0 0 0-1.18l-1.17-1.17a.83.83 0 0 0-1.18 0l-1.17 1.17a.83.83 0 0 0 0 1.18l.82.82-3.61 5.42-4.41-3.07Z" />
</svg>
)
)
);
export const ColorlessCrownIcon = LazyComponent(
() => React.memo(
() => (
<svg width="100%" height="100%" viewBox="1.35 2 21 21" fill="currentColor">
<path d="M5 18a1 1 0 0 0-1 1 3 3 0 0 0 3 3h10a3 3 0 0 0 3-3 1 1 0 0 0-1-1H5ZM3.04 7.76a1 1 0 0 0-1.52 1.15l2.25 6.42a1 1 0 0 0 .94.67h14.55a1 1 0 0 0 .95-.71l1.94-6.45a1 1 0 0 0-1.55-1.1l-4.11 3-3.55-5.33.82-.82a.83.83 0 0 0 0-1.18l-1.17-1.17a.83.83 0 0 0-1.18 0l-1.17 1.17a.83.83 0 0 0 0 1.18l.82.82-3.61 5.42-4.41-3.07Z" />
</svg>
)
)
);
export const HalfedCrownIcon = LazyComponent(
() => React.memo(
() => (
<svg style={{ position: "absolute" }} width="100%" height="100%" viewBox="0 0 512 512" fill={getColor(TAGS.GUILD_OWNER)}>
<path d="M259.7,392.5c-56.9,0-113.8,0-170.7,0c-13.5,0-24.4,10.9-24.4,24.4c0,40.4,32.7,73.1,73.1,73.1c40.6,0,81.3,0,121.9,0 M259.7,2.3c-6.5,0-11.5,3.1-14.3,5.9c-0.1,0.1-0.1,0.1-0.2,0.2l-28.5,28.5c-7.9,7.9-8,20.7-0.2,28.6c0.1,0.1,0.1,0.1,0.2,0.2l20,20l-88,132.1L41.2,142.9h0c-11-7.8-26.2-5.2-34,5.8c-4.6,6.5-5.7,14.7-3.1,22.2L59,327.4c3.4,9.7,12.6,16.3,22.9,16.3c59.2,0,118.5,0,177.7,0" />
</svg>
)
)
);
export const ClydeIcon = LazyComponent(
() => React.memo(
() => (
<svg width="100%" height="100%" viewBox="123.5 152.5 361 270.9">
<path fill="#76C9FC" d="M473.428 271.37c3.101 3.249 5.933 7.127 7.6 11.323 3.138 7.893 3.508 24.798 3.318 33.589-.129 5.947-2.693 13.984-6.768 18.399-.554-4.986-.265-10.3-.372-15.328-.15-7.116-.569-14.325-1.028-21.43q-.472-6.852-1.343-13.664c-.571-4.263-1.403-8.581-1.407-12.889zM249.078 172.759c.179-.048.359-.092.537-.144 2.251-.654 4.711-2.504 6.73-3.746 6.202-3.818 12.426-8.474 19.075-11.406 4.49-1.98 9.499-3.419 14.376-3.995 8.209-.968 17.062-.039 25.316.274 6.915.263 14.243.09 21.069 1.217 4.618.762 9.226 2.112 13.415 4.224 4.099 2.065 8.055 5.042 9.551 9.57.627 1.899.807 4.079.935 6.069-5.611-.256-11.196-1.016-16.826-1.243-16.417-.664-32.802-.597-49.228-.593l-27.881.006c-4.643.046-10.671.783-15.124-.029-.482-.088-.921-.16-1.413-.183l-.532-.021z" />
<path fill="#e2e3ed" d="m249.078 172.759.532.021c.492.023.931.095 1.413.183 4.453.812 10.481.075 15.124.029l27.881-.006c16.426-.004 32.811-.071 49.228.593 5.63.227 11.215.987 16.826 1.243 20.305 2.191 58.1 11.056 75.545 21.193q2.61 1.537 5.067 3.308 2.457 1.771 4.74 3.761 2.284 1.99 4.375 4.181 2.091 2.192 3.971 4.566c11.452 14.294 14.374 31.701 17.345 49.211.478 2.816.737 8.028 2.303 10.328.004 4.308.836 8.626 1.407 12.889q.871 6.812 1.343 13.664c.459 7.105.878 14.314 1.028 21.43.107 5.028-.182 10.342.372 15.328-.43 4.188-.418 8.402-.987 12.576-1.617 11.84-7.086 23.253-16.471 30.89-6.843 5.568-16.399 9.501-24.282 13.557l-22.094 11.369c-4.622 2.372-9.215 4.913-14.029 6.873-10.464 4.261-33.219 9.035-44.322 9.698-6.219 1.291-12.797 1.619-19.123 2.062q-26.881 1.816-53.789.455c-5.231-.227-10.969-.111-16.097-1.053-24.824-.802-74.759-9.048-95.535-21.92q-1.185-.737-2.336-1.526-1.152-.788-2.267-1.626-1.116-.838-2.194-1.724-1.078-.886-2.117-1.819-1.038-.932-2.035-1.909-.996-.977-1.949-1.996-.953-1.02-1.861-2.08-.907-1.06-1.767-2.159-.861-1.098-1.672-2.234-.812-1.135-1.573-2.305-.761-1.17-1.47-2.372-.709-1.201-1.365-2.433-.656-1.232-1.257-2.491-.602-1.259-1.147-2.544-.546-1.284-1.035-2.591-.489-1.307-.921-2.634c-1.208-3.635-1.938-7.413-2.819-11.136-.628-.732-1.452-1.408-2.15-2.081-5.329-5.137-10.41-12.504-10.549-20.194q-1.34-4.538-2.287-9.174c-2.579-12.929-3.767-29.393 1.048-41.791 1.677-4.317 4.219-8.065 6.77-11.894q.087-1.68.204-3.359c.423-14.283 1.715-26.1 10.871-37.76 5.176-6.591 17.559-15.531 24.433-21.114l15.317-12.428c3.582-2.902 7.12-5.972 10.994-8.48 14.664-9.495 36.431-10.485 53.363-12.575z" />
<path fill="#76C9FC" d="M234.311 223.89c-1.042-1.335-.762-3.533-.562-5.145.877-7.043 3.348-13.422 9.133-17.847 5.341-4.086 12.96-5.336 19.445-6.545 32.196-6.005 72.531-4.478 104.968.088 13.88 1.953 41.277 7.859 52.897 14.919 3.58 2.175 6.762 5.265 9.253 8.619 10.643 14.328 15.996 66.98 13.363 84.69-.158 1.06-.307 2.293-1.205 3.005-.237.189-.398.259-.655.391l.03-.316-.279.159-.278 1.298c.101-17.85-1.21-35.242-4.835-52.743-1.01-4.873-2.043-9.887-3.713-14.58-3.04-8.544-8.925-15.7-17.206-19.576-34.82-16.299-130.185-23.208-166.912-9.832-6.762 2.463-10.454 7.053-13.444 13.415z" />
<path fill="#49AFFE" d="M266.384 421.108c2.837-3.947 6.992-7.512 10.425-10.998 7.329-7.442 14.693-14.772 21.17-22.984 2.94-3.727 6.297-10.163 11.131-11.454 4.14-1.106 21.552.218 27.274.35q14.693.348 29.389.21c3.54-.03 14.762-1.055 17.479.576 2.254 1.352 3.042 3.675 3.643 6.076-5.018 8.772-11.535 15.515-18.282 22.896-4.286 4.688-8.367 9.424-13.292 13.463l.072.401c-6.219 1.291-12.797 1.619-19.123 2.062q-26.881 1.816-53.789.455c-5.231-.227-10.969-.111-16.097-1.053z" />
<path fill="#76C9FC" d="M134.1 265.116c.009.757-.164 2.004.306 2.567 5.187-4.579 7.966-8.004 15.001-9.688 1.53-.366 3.211-.741 4.787-.615 4.241.339 7.706 4.098 10.2 7.238 3.215 4.048 5.631 8.851 7.439 13.677 2.905 7.751 5.945 22.594 5.38 30.967.163 15.783-1.342 37.327-12.77 49.293-1.688 1.766-3.174 2.172-5.607 2.125-6.87-.131-12.013-3.919-17.772-7.071-.628-.732-1.452-1.408-2.15-2.081-5.329-5.137-10.41-12.504-10.549-20.194q-1.34-4.538-2.287-9.174c-2.579-12.929-3.767-29.393 1.048-41.791 1.677-4.317 4.219-8.065 6.77-11.894q.087-1.68.204-3.359z" />
<path fill="#49AFFE" d="M128.365 331.334c1.226 1.977 2.253 4.082 3.55 6.016 1.289 1.921 3.139 4.132 5.566 4.513 1.51.236 2.902-.275 4.064-1.234 6.718-5.544 6.064-18.905 6.894-26.805.844 1.051-.733 11.706-.536 13.959.441-.238.259-.111.676-.473.103-.09.204-.182.306-.273.422.243.897.637 1.39.65 1.989.054 4.436-.768 6.431-1.099 2.683-.445 11.582-1.302 13.744.167.871.593 1.347.925 2.437.827.927-1.664 2.177-3.065 2.91-4.852 1.007-2.456 1.461-9.212 1.008-11.972-.256-1.56-.194-2.922-.112-4.493l-.371.134.426-.574c.045 1.1.117 2.108.395 3.177.022.087.047.173.07.26.163 15.783-1.342 37.327-12.77 49.293-1.688 1.766-3.174 2.172-5.607 2.125-6.87-.131-12.013-3.919-17.772-7.071-.628-.732-1.452-1.408-2.15-2.081-5.329-5.137-10.41-12.504-10.549-20.194z" />
<path d="M234.311 223.89c2.99-6.362 6.682-10.952 13.444-13.415 36.727-13.376 132.092-6.467 166.912 9.832 8.281 3.876 14.166 11.032 17.206 19.576 1.67 4.693 2.703 9.707 3.713 14.58 3.625 17.501 4.936 34.893 4.835 52.743-.126 4.126-.437 8.372-1.673 12.334-1.757 5.63-5.371 10.386-10.654 13.108-5.565 2.869-12.626 4.156-18.744 5.393-33.118 6.699-82.7 4.536-115.957-1.774q-13.831-2.651-27.218-7.025c-10.762-3.523-19.167-8.025-24.445-18.57-7.989-15.96-13.083-69.919-7.419-86.782z" />
<path fill="#58F38A" d="M384.35 248.225c1.812-.069 3.417.079 5.077.872 3.575 1.707 6.051 5.152 7.281 8.845 1.28 3.842 1.533 8.395-.436 12.044-1.383 2.564-3.418 3.94-6.155 4.765-1.6.019-2.928-.027-4.426-.683-3.586-1.569-6.269-5.089-7.56-8.712-1.328-3.729-1.569-8.31.168-11.929 1.271-2.649 3.323-4.243 6.051-5.202zM291.509 242.908c1.798-.261 3.426-.164 5.126.522 3.545 1.43 6.121 4.555 7.538 8.035 1.602 3.934 1.912 8.551.14 12.486-1.243 2.76-3.164 4.358-5.93 5.458-1.673.237-3.083.19-4.703-.368-3.534-1.218-6.117-4.177-7.632-7.51-1.788-3.932-2.265-8.715-.608-12.772 1.193-2.921 3.181-4.699 6.069-5.851zM320.722 281.787c6.572-.053 13.173.456 19.732.846 9.679.575 19.325 1.19 28.968 2.229.01.155.022.31.03.465.283 5.587-1.791 10.18-5.503 14.239-3.255 3.06-7.273 4.716-11.654 5.378-7.308 1.104-14.797-.277-20.8-4.71-6.062-4.477-9.649-11.078-10.773-18.447z" />
</svg>
)
)
);
// that's maximum of my creative vision :)
export const OfficialIcon = LazyComponent(
() => React.memo(
() => (
<>
<svg width="100%" height="100%" viewBox="4 4 16 16">
<path fill="#fff" fill-rule="evenodd" d="M19.06 6.94a1.5 1.5 0 0 1 0 2.12l-8 8a1.5 1.5 0 0 1-2.12 0l-4-4a1.5 1.5 0 0 1 2.12-2.12L10 13.88l6.94-6.94a1.5 1.5 0 0 1 2.12 0Z" />
</svg>
<svg width="100%" height="100%" viewBox="0 0 256 198.5">
<path fill="#5865F2" d="M216.856339,16.5966031 C200.285002,8.84328665 182.566144,3.2084988 164.041564,0 C161.766523,4.11318106 159.108624,9.64549908 157.276099,14.0464379 C137.583995,11.0849896 118.072967,11.0849896 98.7430163,14.0464379 C96.9108417,9.64549908 94.1925838,4.11318106 91.8971895,0 C73.3526068,3.2084988 55.6133949,8.86399117 39.0420583,16.6376612 C5.61752293,67.146514 -3.4433191,116.400813 1.08711069,164.955721 C23.2560196,181.510915 44.7403634,191.567697 65.8621325,198.148576 C71.0772151,190.971126 75.7283628,183.341335 79.7352139,175.300261 C72.104019,172.400575 64.7949724,168.822202 57.8887866,164.667963 C59.7209612,163.310589 61.5131304,161.891452 63.2445898,160.431257 C105.36741,180.133187 151.134928,180.133187 192.754523,160.431257 C194.506336,161.891452 196.298154,163.310589 198.110326,164.667963 C191.183787,168.842556 183.854737,172.420929 176.223542,175.320965 C180.230393,183.341335 184.861538,190.991831 190.096624,198.16893 C211.238746,191.588051 232.743023,181.531619 254.911949,164.955721 C260.227747,108.668201 245.831087,59.8662432 216.856339,16.5966031 Z M85.4738752,135.09489 C72.8290281,135.09489 62.4592217,123.290155 62.4592217,108.914901 C62.4592217,94.5396472 72.607595,82.7145587 85.4738752,82.7145587 C98.3405064,82.7145587 108.709962,94.5189427 108.488529,108.914901 C108.508531,123.290155 98.3405064,135.09489 85.4738752,135.09489 Z M170.525237,135.09489 C157.88039,135.09489 147.510584,123.290155 147.510584,108.914901 C147.510584,94.5396472 157.658606,82.7145587 170.525237,82.7145587 C183.391518,82.7145587 193.761324,94.5189427 193.539891,108.914901 C193.539891,123.290155 183.391518,135.09489 170.525237,135.09489 Z" />
</svg>
</>
)
)
);
export const AutoModIcon = LazyComponent(
() => React.memo(
() => (
<>
<svg width="100%" height="100%" viewBox="4 4 16 16">
<path fill="#fff" fill-rule="evenodd" d="M19.06 6.94a1.5 1.5 0 0 1 0 2.12l-8 8a1.5 1.5 0 0 1-2.12 0l-4-4a1.5 1.5 0 0 1 2.12-2.12L10 13.88l6.94-6.94a1.5 1.5 0 0 1 2.12 0Z" />
</svg>
<svg width="100%" height="100%" viewBox="0 0 145.68 106.86">
<path fill="#5865f2"
d="M0,42.57C-.08,4.42,4.45,3.88,8.6,1.8,12.77.25,14.6,0,22.13.08c0,0,45.38-.2,78.45.12,16.08,0,21.38,13,21.38,13S143.24,60,144.68,74.37c3.07,17.75-1.91,25.22-1.91,25.22a15.69,15.69,0,0,1-11.68,7.21c-57.81.06-95.94.06-95.94.06-15.31-1-28.79-7.59-32.36-24.57C2.79,82.29,1,79.29,0,42.57Z" />
<path
d="M21.13.08c42.24-.18,73,0,78.27.11a13.16,13.16,0,0,1,7.66,2.24,20.23,20.23,0,0,1,3.28,3.41c2.84,3.78,15.37,29.4,26.4,68,.62,2.46.57,4.36-.79,5.85a5.19,5.19,0,0,1-3.14,1.57c-16.47,0-54.06,0-81.79-.13-9.51,0-10-7-10.13-8.13C36.11,39.71,37,34.22,32.15,13.22,30.09,8,32.05,2.88,21.13.08Z" />
<path fill="#81f5a5"
d="M59.79,30.59c-.47,3.18-.45,7.19-4.72,7.15-4.19.69-10.31-7-9.58-14.37-.13-2.49.58-6.67,4.72-6.95C54.62,16.37,60,23.63,59.79,30.59Z" />
<path fill="#81f5a5"
d="M104.81,37.59c-4-.13-9.55-7-9.23-14.22,0-2.78.7-6.72,4.59-7.15,4.06-.68,9.54,7,9.45,14.13C109.38,33.48,109,37.68,104.81,37.59Z" />
<path fill="#81f5a5"
d="M62.55,49.42c14,0,29-.06,39.79-.05.3,4.9-1.74,19.56-14.17,19C78,68.93,65.53,57.12,62.55,49.42Z" />
</svg>
</>
)
)
);
export const ErrorIcon = LazyComponent(
() => React.memo(
() => (
(
<Tooltip
text={"`EnhancedUserTags` has just broken down 😔"}>
{({ onMouseEnter, onMouseLeave }) => (
<span
style={{
display: "inline-flex",
position: "relative",
minWidth: "16px",
height: "16px",
}}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<svg width="100%" height="100%" viewBox="0 0 24 24">
<circle fill="#fff" cx="12" cy="12" r="8" />
<path fill="#f00" fill-rule="evenodd"
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm-1.5-5.009c0-.867.659-1.491 1.491-1.491.85 0 1.509.624 1.509 1.491 0 .867-.659 1.509-1.509 1.509-.832 0-1.491-.642-1.491-1.509zM11.172 6a.5.5 0 0 0-.499.522l.306 7a.5.5 0 0 0 .5.478h1.043a.5.5 0 0 0 .5-.478l.305-7a.5.5 0 0 0-.5-.522h-1.655z" />
</svg>
</span>
)}
</Tooltip>
)
)
)
);
export const tagIcons = {
[TAGS.THREAD_CREATOR]: createColoredCrownIcon(TAGS.THREAD_CREATOR),
[TAGS.POST_CREATOR]: createColoredCrownIcon(TAGS.POST_CREATOR),
[TAGS.MODERATOR]: createColoredCrownIcon(TAGS.MODERATOR),
[TAGS.ADMINISTRATOR]: createColoredCrownIcon(TAGS.ADMINISTRATOR),
[TAGS.GROUP_OWNER]: createColoredCrownIcon(TAGS.GROUP_OWNER),
[TAGS.GUILD_OWNER]: createColoredCrownIcon(TAGS.GUILD_OWNER),
[TAGS.BOT]: createColoredCrownIcon(TAGS.BOT),
[TAGS.WEBHOOK]: createColoredCrownIcon(TAGS.WEBHOOK),
[TAGS.CLYDE]: ClydeIcon,
[TAGS.AUTOMOD]: AutoModIcon,
[TAGS.OFFICIAL]: OfficialIcon,
};

View file

@ -0,0 +1,67 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { LazyComponent } from "@utils/lazyReact";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { React } from "@webpack/common";
const OriginalDiscordTag: ({
className,
type,
verified,
useRemSizes,
}: {
className: string;
type: number;
verified?: boolean;
useRemSizes?: boolean;
}) => React.JSX.Element = findByCodeLazy(".Messages.DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL", ".SYSTEM_DM_TAG_OFFICIAL");
const DISCORD_TAG_TYPES: {
SYSTEM_DM: 2;
} = findByPropsLazy("SYSTEM_DM", "STAFF_ONLY_DM");
const USERNAME_COMPONENT_CLASS_NAMES: {
decorator: string;
} = findByPropsLazy("decorator", "avatarWithText");
export const OriginalUsernameSystemTag = LazyComponent(
() => React.memo(
() => <OriginalDiscordTag
className={USERNAME_COMPONENT_CLASS_NAMES.decorator}
type={DISCORD_TAG_TYPES.SYSTEM_DM}
verified={true}
/>
)
);
const MESSAGE_COMPONENT_CLASS_NAMES: {
botTagCompact: string;
botTagCozy: string;
} = findByPropsLazy("botTagCompact", "botTagCozy");
export const OriginalMessageSystemTag = LazyComponent(
() => React.memo(
() => <OriginalDiscordTag
className={MESSAGE_COMPONENT_CLASS_NAMES.botTagCompact}
type={DISCORD_TAG_TYPES.SYSTEM_DM}
verified={true}
/>
)
);
const AUTOMOD_MESSAGE_COMPONENT_CLASS_NAMES: {
systemTag: string;
} = findByPropsLazy("systemTag", "alertActionIcon");
export const OriginalAutoModMessageTag = LazyComponent(
() => React.memo(
() => <OriginalDiscordTag
className={AUTOMOD_MESSAGE_COMPONENT_CLASS_NAMES.systemTag}
type={DISCORD_TAG_TYPES.SYSTEM_DM}
/>
)
);

View file

@ -0,0 +1,336 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getCurrentGuild } from "@utils/discord";
import definePlugin from "@utils/types";
import { ChannelStore, GuildStore } from "@webpack/common";
import type { Message } from "discord-types/general";
import EnhancedUserTag from "./components/EnhancedUserTag";
import { ErrorIcon, tagIcons } from "./components/Icons";
import { OriginalAutoModMessageTag, OriginalMessageSystemTag, OriginalUsernameSystemTag } from "./components/OriginalSystemTag";
import settings from "./settings";
import { getTagDetails, GetTagDetailsReturn, TAG_NAMES, TAGS } from "./tag";
import { Channel, User } from "./types";
import { isAutoContentModerationMessage, isAutoModMessage, isSystemMessage } from "./util/system";
const MembersList = ({
user,
channel,
guildId,
}: {
user: User;
channel: Channel;
guildId: string;
}) => {
const guild = GuildStore.getGuild(guildId);
const tagDetails = getTagDetails({
user,
// not sure that in members list need to append permissions from current channel to compute user permissions
channel: guild ? null : channel,
guild,
});
if (!tagDetails) return;
return <EnhancedUserTag
{...tagDetails}
style={{
display: "inline-flex",
position: "relative",
minWidth: "12px",
height: "12px",
marginLeft: "4px",
}}
/>;
};
const ChannelChat = ({
message,
compact,
usernameClassName,
}: {
message?: Message;
compact?: boolean;
usernameClassName?: string;
}) => {
if (!message) return;
const channel = ChannelStore.getChannel(message.channel_id) as Channel;
if (!channel) return;
// not sure that much ppl wants group owner icon for a single user in the whole dm group chat
if (channel.isGroupDM()) return;
let tagDetails: GetTagDetailsReturn;
// Shows tag also in user message inside AutoMod messages
if (isAutoModMessage(message)) {
// Original official tag for AutoMod is hardcoded and handles in `AutoMod` patch
// so here we apply tag only for users inside `has blocked a message in` message
if (!isAutoContentModerationMessage(message) || !usernameClassName) {
return;
}
tagDetails = getTagDetails({
user: message.author as User,
channel,
guild: GuildStore.getGuild(channel.guild_id),
});
// Community Updates (isSystemMessage instead .system cos there're two CU bots and for first one .system and etc are false)
} else if (isSystemMessage(message)) {
if (settings.store.originalOfficialTag)
return <OriginalMessageSystemTag />;
tagDetails = {
icon: tagIcons[TAGS.OFFICIAL],
text: `${TAG_NAMES[TAGS.OFFICIAL]} Message`,
gap: true,
};
} else {
tagDetails = getTagDetails({
user: message.author as User,
channel,
guild: GuildStore.getGuild(channel.guild_id),
});
}
if (!tagDetails) return;
return <EnhancedUserTag
{...tagDetails}
style={{
display: "inline-flex",
position: "relative",
minWidth: "14px",
height: "14px",
top: "1.5px",
marginLeft: compact ? undefined : "4px",
marginRight: ".35rem",
}}
/>;
};
const AutoMod = ({
compact,
}: {
compact?: boolean;
}) => {
return settings.store.originalAutoModTag ? <OriginalAutoModMessageTag /> : <EnhancedUserTag
icon={tagIcons[TAGS.AUTOMOD]}
text={TAG_NAMES[TAGS.AUTOMOD]}
style={{
display: "inline-flex",
position: "relative",
minWidth: "14px",
height: "14px",
marginLeft: compact ? undefined : "4px",
marginRight: ".35rem",
alignSelf: "center",
bottom: "1px",
gap: "4px",
}}
/>;
};
const VoiceChannels = ({
user,
guildId,
}: {
user: User;
guildId: string;
}) => {
const tagDetails = getTagDetails({
user,
guild: GuildStore.getGuild(guildId)
});
if (!tagDetails) return;
return <EnhancedUserTag
{...tagDetails}
style={{
display: "inline-flex",
position: "relative",
minWidth: "14px",
height: "14px",
marginLeft: "4px",
}}
/>;
};
const UserProfile = ({ user, tags }: {
user: User;
tags?: {
props?: {
displayProfile?: {
_guildMemberProfile?: any;
};
};
};
}) => {
// Show nothing in user `Main Profile`
// p.s. there's no other way to check if profile is server or main
const displayProfile = tags?.props?.displayProfile;
const isMainProfile = displayProfile ? !displayProfile._guildMemberProfile : false;
if (!user.bot && isMainProfile) return;
const tagDetails = getTagDetails({
user,
guild: getCurrentGuild()
});
if (!tagDetails) return;
return <EnhancedUserTag
{...tagDetails}
style={{
display: "inline-flex",
minWidth: "16px",
height: "16px",
}}
/>;
};
const DMsList = ({
channel,
user
}: {
channel: Channel;
user: User;
}) => {
if (channel.isSystemDM())
return settings.store.originalOfficialTag ? <OriginalUsernameSystemTag /> : <EnhancedUserTag
icon={tagIcons[TAGS.OFFICIAL]}
text={`${TAG_NAMES[TAGS.OFFICIAL]} Account`}
gap={true}
style={{
display: "inline-flex",
position: "relative",
minWidth: "14px",
height: "14px",
marginLeft: "4px",
}}
/>;
if (user?.bot && settings.store.botTagInDmsList)
return <EnhancedUserTag
icon={tagIcons[TAGS.BOT]}
text={TAG_NAMES[TAGS.BOT]}
style={{
display: "inline-flex",
position: "relative",
minWidth: "14px",
height: "14px",
marginLeft: "4px",
}}
/>;
};
export default definePlugin({
name: "EnhancedUserTags",
description: "Replaces and extends default tags (Official, Original Poster, etc.) with a crown icon, the type of which depends on the user's permissions",
authors: [Devs.Vishnya],
settings,
patches: [
// Members List
{
find: ".Messages.GUILD_OWNER,",
replacement: [
// Remove original owner crown icon
{
match: /=\(\)=>null.{1,80}\.Messages\.GUILD_OWNER.*?\.ownerIcon.*?:null,/,
replace: "=()=>null,",
},
// Add new tag
{
match: /=\(\)=>{let.{1,30}isClyde.*?isVerifiedBot.*?},/,
replace: "=()=>$self.membersList(arguments[0]),",
},
],
},
// Chat
{
// Remove original tag
find: ".clanTagChiplet,profileViewedAnalytics:",
replacement: [
{
// Cozy view
match: /:null,null==\i\|\|\i\?null:\i,/,
replace: ":null,",
},
{
// Compact view
match: /children:\[null!=.{1,50}?" ".*?:null,/,
replace: "children:[",
},
],
},
{
// Add new one
find: ".nitroAuthorBadgeTootip,",
replacement: {
match: /"span",{id:\i.{1,30}?\i}\),/,
replace: "$&$self.channelChat(arguments[0]),",
},
},
{
// AutoMod
find: ".SYSTEM_DM,className", // x5
all: true,
noWarn: true,
replacement: {
match: /(GUILD_AUTOMOD_USERNAME}\),).{1,30}.SYSTEM_DM.*?}\)/,
replace: "$1$self.autoMod(arguments[0])",
},
},
// Guild channels list > Voice user
{
find: ".WATCH_STREAM_WATCHING,",
replacement: {
match: /isSelf:\i}\)}\):null/,
replace: "$&,$self.voiceChannels(this.props)",
},
},
// Popout/modal profile
{
find: ".Messages.USER_PROFILE_PRONOUNS",
replacement: {
match: /null!=\i&&\(0.{1,35}.isVerifiedBot\(\)}\)/,
replace: "$self.userProfile(arguments[0]),",
},
},
// DMs list
{
find: "PrivateChannel.renderAvatar: Invalid prop configuration",
replacement: {
match: /decorators:\i.isSystemDM\(\).{1,80}}\):null/,
replace: "decorators:$self.dmsList(arguments[0])"
},
},
],
membersList: ErrorBoundary.wrap(MembersList, { fallback: () => <ErrorIcon /> }),
channelChat: ErrorBoundary.wrap(ChannelChat, { fallback: () => <ErrorIcon /> }),
autoMod: ErrorBoundary.wrap(AutoMod, { fallback: () => <ErrorIcon /> }),
voiceChannels: ErrorBoundary.wrap(VoiceChannels, { fallback: () => <ErrorIcon /> }),
userProfile: ErrorBoundary.wrap(UserProfile, { fallback: () => <ErrorIcon /> }),
dmsList: ErrorBoundary.wrap(DMsList, { fallback: () => <ErrorIcon /> }),
});

View file

@ -0,0 +1,44 @@
/*
* 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";
import ColorSettings from "./components/ColorSettings";
export default definePluginSettings({
ignoreYourself: {
type: OptionType.BOOLEAN,
description:
"Don't add tag to yourself",
default: false,
},
botTagInDmsList: {
type: OptionType.BOOLEAN,
description:
"Show bot tag in DMs list",
default: true,
},
originalOfficialTag: {
type: OptionType.BOOLEAN,
description:
"Use the original `Official Discord` tag for official messages",
default: false,
},
originalAutoModTag: {
type: OptionType.BOOLEAN,
description:
"Use the original `Official Discord` tag for AutoMod messages",
default: false,
},
colorSettings: {
type: OptionType.COMPONENT,
description: "",
component: () => {
return <ColorSettings />;
}
}
});

View file

@ -0,0 +1,264 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { UserStore } from "@webpack/common";
import { Guild } from "discord-types/general";
import { tagIcons } from "./components/Icons";
import { OriginalMessageSystemTag, OriginalUsernameSystemTag } from "./components/OriginalSystemTag";
import settings from "./settings";
import { Channel, User } from "./types";
import { computePermissions, MODERATOR_PERMISSIONS_BITS, PERMISSIONS_BITS } from "./util/permissions";
export enum TAGS {
THREAD_CREATOR = 1,
POST_CREATOR,
MODERATOR,
ADMINISTRATOR,
GROUP_OWNER, // DM group
GUILD_OWNER,
BOT,
WEBHOOK,
CLYDE,
AUTOMOD,
OFFICIAL,
}
export const TAG_NAMES = {
[TAGS.THREAD_CREATOR]: "Thread Creator",
[TAGS.POST_CREATOR]: "Post Creator",
[TAGS.MODERATOR]: "Moderator",
[TAGS.ADMINISTRATOR]: "Administrator",
[TAGS.GROUP_OWNER]: "Group Owner",
[TAGS.GUILD_OWNER]: "Server Owner",
[TAGS.BOT]: "Bot",
[TAGS.WEBHOOK]: "Webhook",
[TAGS.CLYDE]: "Clyde",
[TAGS.AUTOMOD]: "Official AutoMod Message",
[TAGS.OFFICIAL]: "Official Discord",
};
// i18n.MODERATE_MEMBERS and i18n.MANAGE_GUILD is "" so I decided to define them all manually
// Array instead dict to have strict order
const MODERATOR_PERMISSIONS_NAMES: [bigint, string][] = [
[PERMISSIONS_BITS.MANAGE_GUILD, "Manage Guild"],
[PERMISSIONS_BITS.MANAGE_CHANNELS, "Manage Channels"],
[PERMISSIONS_BITS.MANAGE_ROLES, "Manage Roles"],
[PERMISSIONS_BITS.MANAGE_MESSAGES, "Manage Messages"],
[PERMISSIONS_BITS.BAN_MEMBERS, "Ban Members"],
[PERMISSIONS_BITS.KICK_MEMBERS, "Kick Members"],
[PERMISSIONS_BITS.MODERATE_MEMBERS, "Timeout Members"],
];
const permissions2Text = (permissions: bigint): string => {
return MODERATOR_PERMISSIONS_NAMES.filter(([bit]) => permissions & bit)
.map(([, name], i, array) =>
i === array.length - 1 ? name : `${name}, ${i % 2 === 0 ? "\n" : ""}`
)
.join("");
};
export interface TagDetails {
icon: React.ComponentType;
text?: string | null;
gap?: boolean | null;
halfGold?: boolean | null;
}
export type GetTagDetailsReturn = TagDetails | undefined | null;
const getTagDeailtsForPostOrThread = ({
tag,
perms,
isBot,
isGuildOwner,
isAdministrator,
isModerator,
}: {
tag: TAGS.POST_CREATOR | TAGS.THREAD_CREATOR,
perms: bigint;
isBot: boolean;
isGuildOwner: boolean;
isAdministrator: boolean;
isModerator: boolean;
}): GetTagDetailsReturn => {
if (isBot) {
if (isAdministrator)
return {
icon: tagIcons[TAGS.BOT],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.BOT]} (${TAG_NAMES[TAGS.ADMINISTRATOR]})`,
halfGold: true,
};
if (isModerator)
return {
icon: tagIcons[TAGS.BOT],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.BOT]} (${permissions2Text(perms)})`,
halfGold: true,
};
return {
icon: tagIcons[TAGS.BOT],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.BOT]}`,
halfGold: true,
};
}
if (isGuildOwner)
return {
icon: tagIcons[tag],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.GUILD_OWNER]}`,
};
if (isAdministrator)
return {
icon: tagIcons[TAGS.ADMINISTRATOR],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.ADMINISTRATOR]}`,
halfGold: true,
};
if (isModerator)
return {
icon: tagIcons[TAGS.MODERATOR],
text: `${TAG_NAMES[tag]} | ${TAG_NAMES[TAGS.MODERATOR]} (${permissions2Text(perms)})`,
halfGold: true,
};
return {
icon: tagIcons[tag],
text: TAG_NAMES[tag],
};
};
export const getTagDetails = ({
user,
channel,
guild,
}: {
user?: User | null;
channel?: Channel | null;
guild?: Guild | null;
}): GetTagDetailsReturn => {
if (!user) return;
if (user.id === UserStore.getCurrentUser().id && settings.store.ignoreYourself)
return;
if (user.bot) {
if (user.isSystemUser()) {
if (settings.store.originalOfficialTag) {
return {
icon: channel ? OriginalMessageSystemTag : OriginalUsernameSystemTag,
};
}
return {
icon: tagIcons[TAGS.OFFICIAL],
text: `${TAG_NAMES[TAGS.OFFICIAL]} ${(channel?.isDM() || guild) ? "Message" : "Account"}`,
gap: true,
};
}
if (user.isClyde())
return {
icon: tagIcons[TAGS.CLYDE],
text: TAG_NAMES[TAGS.CLYDE],
};
if (user.isNonUserBot())
return {
icon: tagIcons[TAGS.WEBHOOK],
text: TAG_NAMES[TAGS.WEBHOOK],
};
if (!guild || channel?.isDM() || channel?.isGroupDM())
return {
icon: tagIcons[TAGS.BOT],
text: TAG_NAMES[TAGS.BOT],
};
} else {
if (channel?.isGroupDM())
return channel.ownerId === user.id ? {
icon: tagIcons[TAGS.GROUP_OWNER],
text: TAG_NAMES[TAGS.GROUP_OWNER],
} : null;
if (channel?.isDM()) return;
if (!guild) return;
}
const perms = computePermissions({
user: user,
context: guild,
overwrites: channel ? channel.permissionOverwrites : null,
});
const isGuildOwner = guild.ownerId === user.id;
const isAdministrator = !!(perms & PERMISSIONS_BITS.ADMINISTRATOR);
const isModerator = !!(perms & MODERATOR_PERMISSIONS_BITS);
// I'm 99% sure that only forumPost/thread can have `ownerId` (+ groupDM, but the check for that is above)
// so for 100% I made this shit code :)
// correct me please if my 99% is actually 100%
if (channel && channel.ownerId === user.id) {
if (channel.isForumPost())
return getTagDeailtsForPostOrThread({
tag: TAGS.POST_CREATOR,
perms,
isBot: user.bot,
isGuildOwner,
isAdministrator,
isModerator,
});
if (channel.isThread())
return getTagDeailtsForPostOrThread({
tag: TAGS.THREAD_CREATOR,
perms,
isBot: user.bot,
isGuildOwner,
isAdministrator,
isModerator,
});
}
if (isGuildOwner)
return {
icon: tagIcons[TAGS.GUILD_OWNER],
text: TAG_NAMES[TAGS.GUILD_OWNER],
};
if (isAdministrator) {
if (user.bot)
return {
icon: tagIcons[TAGS.BOT],
text: `${TAG_NAMES[TAGS.BOT]} (${TAG_NAMES[TAGS.ADMINISTRATOR]})`,
};
else
return {
icon: tagIcons[TAGS.ADMINISTRATOR],
text: TAG_NAMES[TAGS.ADMINISTRATOR],
};
}
if (isModerator) {
if (user.bot)
return {
icon: tagIcons[TAGS.BOT],
text: `${TAG_NAMES[TAGS.BOT]} (${permissions2Text(perms)})`,
};
else
return {
icon: tagIcons[TAGS.MODERATOR],
text: `${TAG_NAMES[TAGS.MODERATOR]} (${permissions2Text(perms)})`,
};
}
};

29
src/plugins/enhancedUserTags/types.d.ts vendored Normal file
View file

@ -0,0 +1,29 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type { Channel as OriginalChannel, User as OriginalUser } from "discord-types/general";
import type { TAGS } from "./tag";
export type Channel = {
isForumPost(): boolean;
isGroupDM(): boolean;
} & OriginalChannel;
export type User = {
isClyde(): boolean;
isVerifiedBot(): boolean;
} & OriginalUser;
export type CSSHex = `#${string}`;
type ColoredTagName = keyof Omit<typeof TAGS, "CLYDE" | "AUTOMOD" | "OFFICIAL">;
type CustomColoredTagName = Exclude<ColoredTagName, "THREAD_CREATOR" | "POST_CREATOR" | "GROUP_OWNER" | "GUILD_OWNER">;
export type ColoredTag = (typeof TAGS)[ColoredTagName];
export type CustomColoredTag = (typeof TAGS)[CustomColoredTagName];
export type TagColors = Record<ColoredTag, CSSHex>;

View file

@ -0,0 +1,11 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { CSSHex } from "../types";
export const hex2number = (hex: CSSHex): number => parseInt(hex.slice(1), 16);
export const number2hex = (num: number): CSSHex => `#${num.toString(16).padStart(6, "0")}`;

View file

@ -0,0 +1,37 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByCodeLazy } from "@webpack";
import { Guild } from "discord-types/general";
import { Channel, User } from "../types";
// can't use `PermissionsBits` from `@webpack/common` due to lazy find => throws error in `MODERATOR_PERMISSIONS_BITS`
export const PERMISSIONS_BITS = {
ADMINISTRATOR: 1n << 3n,
MANAGE_GUILD: 1n << 5n,
MANAGE_CHANNELS: 1n << 4n,
MANAGE_ROLES: 1n << 28n,
BAN_MEMBERS: 1n << 2n,
MANAGE_MESSAGES: 1n << 13n,
KICK_MEMBERS: 1n << 1n,
MODERATE_MEMBERS: 1n << 40n,
};
export const MODERATOR_PERMISSIONS_BITS =
PERMISSIONS_BITS.MANAGE_GUILD
| PERMISSIONS_BITS.MANAGE_CHANNELS
| PERMISSIONS_BITS.MANAGE_ROLES
| PERMISSIONS_BITS.MANAGE_MESSAGES
| PERMISSIONS_BITS.BAN_MEMBERS
| PERMISSIONS_BITS.KICK_MEMBERS
| PERMISSIONS_BITS.MODERATE_MEMBERS;
export const computePermissions: (options: {
user?: User | string | null;
context?: Guild | Channel | null;
overwrites?: Channel["permissionOverwrites"] | null;
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");

View file

@ -0,0 +1,27 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByCodeLazy } from "@webpack";
import { Message } from "discord-types/general";
export const isSystemMessage = findByCodeLazy(".messageReference.guild_id===", ".author.id");
// 24 - `Activity Alerts Enabled`/`safety alert`/`has blocked a message in`/
// 36 - `enabled security actions`
// 37 - `disabled security actions`
// 39 - `resolved an Activity Alert`
// I found no more automod message types but mb they're exists
export const SECURITY_ACTION_MESSAGE_TYPES = [24, 36, 37, 39];
export const isAutoModMessage = (msg: Message) => {
return SECURITY_ACTION_MESSAGE_TYPES.includes(msg.type);
};
const AUTO_MODERATION_EMBED_TYPE = "auto_moderation_message";
export const isAutoContentModerationMessage = (msg: Message) => {
return msg.embeds.some(({ type }) => type === AUTO_MODERATION_EMBED_TYPE);
};