Merge branch 'dev' into translation

This commit is contained in:
thororen1234 2025-12-03 13:07:58 -05:00
commit b51d882940
No known key found for this signature in database
96 changed files with 962 additions and 1310 deletions

View file

@ -77,13 +77,22 @@ jobs:
if: steps.check.outputs.release == 'true'
run: pnpm buildWebStandalone
- name: Create sources zip
if: steps.check.outputs.release == 'true'
run: zip -r dist/extension-sources.zip . -x "node_modules/*" -x "dist/*" -x ".git/*"
- name: Publish extension
if: steps.check.outputs.release == 'true'
run: |
cd dist/browser/chromium-unpacked
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
run: pnpx publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip dist/extension-sources.zip --edge-zip dist/extension-chrome.zip
env:
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }}
EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}

View file

@ -1,7 +1,7 @@
{
"name": "equicord",
"private": "true",
"version": "1.13.6.2",
"version": "1.13.6.4",
"description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": {

View file

@ -1,7 +1,7 @@
{
"name": "@equicord/types",
"private": false,
"version": "1.13.4",
"version": "1.13.6",
"description": "",
"types": "index.d.ts",
"scripts": {

View file

@ -17,9 +17,12 @@
*/
import ErrorBoundary from "@components/ErrorBoundary";
import globalBadges from "@equicordplugins/globalBadges";
import BadgeAPIPlugin from "@plugins/_api/badges";
import { ComponentType, HTMLProps } from "react";
import { isPluginEnabled } from "./PluginManager";
export const enum BadgePosition {
START,
END
@ -99,6 +102,18 @@ export function _getBadges(args: BadgeUserArgs) {
const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId);
const equicordDonorBadges = BadgeAPIPlugin.getEquicordDonorBadges(args.userId);
const GlobalBadges = isPluginEnabled(globalBadges.name) ? globalBadges.getGlobalBadges(args.userId) : false;
// do globalbadges first so it shows before the contrib badges but after donor badges
if (GlobalBadges) {
badges.unshift(
...GlobalBadges.map(badge => ({
...args,
...badge,
}))
);
}
if (donorBadges) {
badges.unshift(
...donorBadges.map(badge => ({

62
src/api/HeaderBar.tsx Normal file
View file

@ -0,0 +1,62 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { useEffect, useState } from "@webpack/common";
import type { JSX } from "react";
export type HeaderBarButtonFactory = () => JSX.Element | null;
interface HeaderBarButton {
render: HeaderBarButtonFactory;
priority: number;
}
export const buttons = new Map<string, HeaderBarButton>();
const listeners = new Set<() => void>();
/**
* Add a button to the header bar.
* @param id Unique identifier for the button.
* @param render Function that renders the button component.
* @param priority Higher priority = more to the right. Default is 0.
*/
export function addHeaderBarButton(id: string, render: HeaderBarButtonFactory, priority = 0) {
buttons.set(id, { render, priority });
listeners.forEach(l => l());
}
/**
* Remove a button from the header bar.
* @param id The identifier of the button to remove.
*/
export function removeHeaderBarButton(id: string) {
buttons.delete(id);
listeners.forEach(l => l());
}
function HeaderBarButtons() {
const [, forceUpdate] = useState(0);
useEffect(() => {
const listener = () => forceUpdate(n => n + 1);
listeners.add(listener);
return () => { listeners.delete(listener); };
}, []);
const sorted = Array.from(buttons).sort(([, a], [, b]) => a.priority - b.priority);
return sorted.map(([id, { render: Button }]) => (
<ErrorBoundary noop key={id}>
<Button />
</ErrorBoundary>
));
}
export function _addButtons() {
return [<HeaderBarButtons key="vc-header-bar-buttons" />];
}

View file

@ -40,6 +40,8 @@ import Plugins from "~plugins";
export { Plugins as plugins };
import { traceFunction } from "../debug/Tracer";
import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer";
import { addHeaderBarButton, removeHeaderBarButton } from "./HeaderBar";
const logger = new Logger("PluginManager", "#a6d189");
@ -186,7 +188,9 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
const {
name, commands, contextMenus, managedStyle, userProfileBadges,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, renderHeaderBarButton, audioProcessor
} = p;
if (p.start) {
@ -240,13 +244,17 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
// @ts-expect-error: legacy code doesn't have icon
else if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator);
if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon);
if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration);
if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory);
if (messagePopoverButton) addMessagePopoverButton(name, messagePopoverButton.render, messagePopoverButton.icon);
// @ts-expect-error: legacy code doesn't have icon
else if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
// Custom
if (renderNicknameIcon) addNicknameIcon(name, renderNicknameIcon);
if (renderHeaderBarButton) addHeaderBarButton(name, renderHeaderBarButton);
if (audioProcessor) addAudioProcessor(name, audioProcessor);
return true;
}, p => `startPlugin ${p.name}`);
@ -254,7 +262,9 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
const {
name, commands, contextMenus, managedStyle, userProfileBadges,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton, renderNicknameIcon
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
// Custom
renderNicknameIcon, renderHeaderBarButton, audioProcessor
} = p;
if (p.stop) {
@ -304,11 +314,15 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
if (chatBarButton || renderChatBarButton) removeChatBarButton(name);
if (renderMemberListDecorator) removeMemberListDecorator(name);
if (renderNicknameIcon) removeNicknameIcon(name);
if (renderMessageDecoration) removeMessageDecoration(name);
if (renderMessageAccessory) removeMessageAccessory(name);
if (messagePopoverButton || renderMessagePopoverButton) removeMessagePopoverButton(name);
// Custom
if (renderNicknameIcon) removeNicknameIcon(name);
if (renderHeaderBarButton) removeHeaderBarButton(name);
if (audioProcessor) removeAudioProcessor(name);
return true;
}, p => `stopPlugin ${p.name}`);
@ -318,7 +332,9 @@ export const initPluginManager = onlyOnce(function init() {
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton", "renderNicknameIcon"
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton",
// Custom
"renderNicknameIcon", "renderHeaderBarButton"
];
const neededApiPlugins = new Set<string>();
@ -353,9 +369,13 @@ export const initPluginManager = onlyOnce(function init() {
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
if (p.messagePopoverButton || p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
// Custom
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.renderHeaderBarButton) neededApiPlugins.add("HeaderBarAPI");
if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI");
for (const key of pluginKeysToBind) {
p[key] &&= p[key].bind(p) as any;
}

View file

@ -22,6 +22,7 @@ import * as $ChatButtons from "./ChatButtons";
import * as $Commands from "./Commands";
import * as $ContextMenu from "./ContextMenu";
import * as $DataStore from "./DataStore";
import * as $HeaderBar from "./HeaderBar";
import * as $MemberListDecorators from "./MemberListDecorators";
import * as $MessageAccessories from "./MessageAccessories";
import * as $MessageDecorations from "./MessageDecorations";
@ -129,6 +130,11 @@ export const ContextMenu = $ContextMenu;
*/
export const ChatButtons = $ChatButtons;
/**
* An API allowing you to add buttons to the header bar.
*/
export const HeaderBar = $HeaderBar;
/**
* An API allowing you to update and re-render messages.
*/

59
src/components/Alert.tsx Normal file
View file

@ -0,0 +1,59 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { filters, mapMangledModuleLazy } from "@webpack";
import type { ComponentType, CSSProperties, ReactNode } from "react";
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO", "ERROR"),
HelpMessage: filters.byCode(".iconDiv")
});
export interface AlertProps {
children: ReactNode;
className?: string;
style?: CSSProperties;
icon?: ComponentType<{ className?: string; color?: string; }>;
}
function Warning({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.WARNING} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Info({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.INFO} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Error({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.ERROR} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
function Positive({ children, className, style, icon }: AlertProps) {
return (
<HelpMessage messageType={HelpMessageTypes.POSITIVE} className={className} style={style} icon={icon}>
{children}
</HelpMessage>
);
}
export const Alert = {
Warning,
Info,
Error,
Positive
};

View file

@ -21,6 +21,7 @@ import "./themesStyles.css";
import { isPluginEnabled } from "@api/PluginManager";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard";
@ -44,7 +45,7 @@ import { usercssParse } from "@utils/themes/usercss";
import { t } from "@utils/translation";
import { getStylusWebStoreUrl } from "@utils/web";
import { findLazy } from "@webpack";
import { Alerts, Button, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common";
import { Alerts, React, showToast, TabBar, TextInput, Tooltip, useEffect, useMemo, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react";
import type { UserstyleHeader } from "usercss-meta";
@ -600,7 +601,7 @@ export function CspErrorCard() {
{i !== 0 && <Divider className={Margins.bottom8} />}
<div className="vc-settings-csp-row">
<Link href={url}>{url}</Link>
<Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}>
<Button variant="secondary" onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}>
Allow
</Button>
</div>

View file

@ -90,8 +90,8 @@ function ResetButton({ themeSettings, themeId, close, onReset }: ResetButtonProp
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async () => {
await close(); // close the modal first to stop rendering
onClick={() => {
close(); // close the modal first to stop rendering
delete themeSettings[themeId];
onReset();
}}>

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export * from "./Alert";
export * from "./BaseText";
export * from "./Button";
export * from "./Card";

View file

@ -16,23 +16,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button } from "@components/Button";
import { Heart } from "@components/Heart";
import { openInviteModal } from "@utils/discord";
import { t } from "@utils/translation";
import { ButtonProps } from "@vencord/discord-types";
import { Button, showToast } from "@webpack/common";
import { showToast } from "@webpack/common";
export function DonateButton({
look = Button.Looks.LINK,
color = Button.Colors.TRANSPARENT,
link,
equicord = false,
...props
}: Partial<ButtonProps> & { link: string; }) {
}: Partial<ButtonProps> & { equicord?: boolean; }) {
const link = equicord ? "https://github.com/sponsors/thororen1234" : "https://github.com/sponsors/Vendicated";
return (
<Button
{...props}
look={look}
color={color}
variant="none"
size="medium"
type="button"
onClick={() => VencordNative.native.openExternal(link)}
className="vc-donate-button"
>
@ -43,15 +44,14 @@ export function DonateButton({
}
export function InviteButton({
look = Button.Looks.LINK,
color = Button.Colors.TRANSPARENT,
...props
}: Partial<ButtonProps>) {
return (
<Button
{...props}
look={look}
color={color}
variant="none"
size="medium"
type="button"
onClick={async e => {
e.preventDefault();
openInviteModal("equicord-1173279886065029291").catch(() =>

View file

@ -1,5 +1,6 @@
.vc-donate-button {
overflow: visible !important;
color: var(--text-default)
}
.vc-donate-button .vc-heart-icon {

View file

@ -6,6 +6,7 @@
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
@ -14,7 +15,7 @@ import { ChangeList } from "@utils/ChangeList";
import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react";
import { t } from "@utils/translation";
import { Button, React, Tooltip } from "@webpack/common";
import { React, Tooltip } from "@webpack/common";
import Plugins from "~plugins";
@ -166,8 +167,8 @@ export function NewPluginsSection({
{tooltipProps => (
<Button
{...tooltipProps}
color={Button.Colors.LINK}
size={Button.Sizes.SMALL}
variant="link"
size="small"
onClick={() => location.reload()}
className={Margins.top16}
>

View file

@ -6,6 +6,7 @@
import "./styles.css";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard";
@ -20,7 +21,7 @@ import { Margins } from "@utils/margins";
import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation";
import { getRepo, UpdateLogger } from "@utils/updater";
import { Alerts, Button, React, Toasts } from "@webpack/common";
import { Alerts, React, Toasts } from "@webpack/common";
import {
ChangelogEntry,
@ -116,9 +117,8 @@ function UpdateLogCard({
}
</span>
<Button
size={Button.Sizes.NONE}
color={Button.Colors.TRANSPARENT}
look={Button.Looks.FILLED}
size="min"
variant="secondary"
className="vc-changelog-delete-button"
style={{
padding: "4px",
@ -500,14 +500,10 @@ function ChangelogContent() {
<div className="vc-changelog-controls">
<Button
size={Button.Sizes.SMALL}
size="small"
disabled={isLoading || repoPending || !!repoErr}
onClick={fetchChangelog}
color={
recentlyChecked
? Button.Colors.GREEN
: Button.Colors.BRAND
}
variant={recentlyChecked ? "positive" : "primary"}
>
{isLoading
? t("vencord.loading")
@ -520,20 +516,16 @@ function ChangelogContent() {
{changelogHistory.length > 0 && (
<>
<Button
size={Button.Sizes.SMALL}
color={
showHistory
? Button.Colors.PRIMARY
: Button.Colors.BRAND
}
size="small"
variant={showHistory ? "primary" : "secondary"}
onClick={() => setShowHistory(!showHistory)}
style={{ marginLeft: "8px" }}
>
{showHistory ? t("vencord.logs.hideLogs") : t("vencord.logs.showLogs")}
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
size="small"
variant="dangerPrimary"
onClick={() => {
Alerts.show({
title: t("vencord.logs.clearAllLogs"),

View file

@ -22,6 +22,7 @@ import { generateId } from "@api/Commands";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph";
@ -35,7 +36,7 @@ import { t } from "@utils/translation";
import { OptionType, Plugin } from "@utils/types";
import { User } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { Button, Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
@ -230,8 +231,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
{({ onMouseEnter, onMouseLeave }) => (
<Button
className={cl("disable-warning")}
size={Button.Sizes.SMALL}
color={Button.Colors.BRAND}
size="small"
variant="primary"
onClick={handleResetClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
@ -352,16 +353,15 @@ export function openWarningModal(plugin?: Plugin | null, pluginModalProps?: Moda
<Flex className="vc-button-container">
<Flex className="button-group">
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
size="small"
variant="secondary"
onClick={warningModalProps.onClose}
look={Button.Looks.FILLED}
>
{t("vencord.cancel")}
</Button>
{!Settings.ignoreResetWarning && (
<Button
size={Button.Sizes.SMALL}
size="small"
className={cl("disable-warning")}
onClick={() => {
Settings.ignoreResetWarning = true;
@ -371,7 +371,7 @@ export function openWarningModal(plugin?: Plugin | null, pluginModalProps?: Moda
</Button>
)}
<Button
size={Button.Sizes.SMALL}
size="small"
onClick={() => {
if (isPlugin) {
if (plugin && pluginModalProps)

View file

@ -298,4 +298,4 @@
.vc-plugins-ui-elements {
margin-top: 16px;
}
}

View file

@ -17,66 +17,65 @@
*/
import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline";
import { Card } from "@components/Card";
import { Alert } from "@components/Alert";
import { Button } from "@components/Button";
import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins";
import { Button, Text } from "@webpack/common";
function BackupAndRestoreTab() {
return (
<SettingsTab title="Backup & Restore">
<Flex flexDirection="column" gap="0.5em">
<Card variant="warning">
<Heading tag="h4">Warning</Heading>
<Paragraph>Importing a settings file will overwrite your current settings.</Paragraph>
</Card>
<Alert.Warning>
Importing a settings file will overwrite your current settings.
</Alert.Warning>
<Text variant="text-md/normal" className={Margins.bottom8}>
<Paragraph className={Margins.bottom8}>
You can import and export your Vencord settings as a JSON file.
This allows you to easily transfer your settings to another device,
or recover your settings after reinstalling Vencord or Discord.
</Text>
</Paragraph>
<Heading tag="h4">Settings Export contains:</Heading>
<Text variant="text-md/normal" className={Margins.bottom8}>
<Paragraph className={Margins.bottom8}>
<ul>
<li>&mdash; Custom QuickCSS</li>
<li>&mdash; Theme Links</li>
<li>&mdash; Plugin Settings</li>
</ul>
</Text>
</Paragraph>
<Flex>
<Button
onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL}
size="small"
>
Import Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("settings")}
size={Button.Sizes.SMALL}
size="small"
>
Export Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("plugins")}
size={Button.Sizes.SMALL}
size="small"
>
Export Plugins
</Button>
<Button
onClick={() => downloadSettingsBackup("css")}
size={Button.Sizes.SMALL}
size="small"
>
Export CSS
</Button>
<Button
onClick={() => downloadSettingsBackup("datastore")}
size={Button.Sizes.SMALL}
size="small"
>
Export DataStores
</Button>

View file

@ -19,6 +19,7 @@
import { useSettings } from "@api/Settings";
import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
import { Button } from "@components/Button";
import { CheckedTextInput } from "@components/CheckedTextInput";
import { Divider } from "@components/Divider";
import { FormSwitch } from "@components/FormSwitch";
@ -28,7 +29,7 @@ import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins";
import { Alerts, Button, Tooltip, useState } from "@webpack/common";
import { Alerts, Tooltip, useState } from "@webpack/common";
function validateUrl(url: string) {
try {
@ -60,7 +61,7 @@ function SettingsSyncSection() {
/>
<div className="vc-cloud-settings-sync-grid">
<Button
size={Button.Sizes.SMALL}
size="small"
disabled={!sectionEnabled}
onClick={() => putCloudSettings(true)}
>
@ -71,8 +72,8 @@ function SettingsSyncSection() {
<Button
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
size="small"
variant="dangerPrimary"
disabled={!sectionEnabled}
onClick={() => getCloudSettings(true, true)}
>
@ -81,8 +82,8 @@ function SettingsSyncSection() {
)}
</Tooltip>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
size="small"
variant="dangerPrimary"
disabled={!sectionEnabled}
onClick={() => deleteCloudSettings()}
>
@ -152,7 +153,7 @@ function CloudTab() {
<Grid columns={2} gap="1em" className={Margins.top8}>
<Button
size={Button.Sizes.MEDIUM}
size="medium"
disabled={!settings.cloud.authenticated}
onClick={async () => {
settings.cloud.authenticated = false;
@ -163,8 +164,8 @@ function CloudTab() {
Reauthorize
</Button>
<Button
size={Button.Sizes.MEDIUM}
color={Button.Colors.RED}
size="medium"
variant="dangerPrimary"
disabled={!settings.cloud.authenticated}
onClick={() => Alerts.show({
title: "Are you sure?",
@ -177,10 +178,10 @@ function CloudTab() {
>
Erase All Data
</Button>
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://cloud.equicord.org/")}>
<Button size="medium" onClick={() => changeUrl("https://cloud.equicord.org/")}>
Use Equicord's Cloud
</Button>
<Button size={Button.Sizes.MEDIUM} onClick={() => changeUrl("https://api.vencord.dev/")}>
<Button size="medium" onClick={() => changeUrl("https://api.vencord.dev/")}>
Use Vencord's Cloud
</Button>
</Grid>

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
@ -13,7 +14,7 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { relaunch } from "@utils/native";
import { changes, checkForUpdates, update, updateError } from "@utils/updater";
import { Alerts, Button, React, Toasts, useState } from "@webpack/common";
import { Alerts, React, Toasts, useState } from "@webpack/common";
import { runWithDispatch } from "./runWithDispatch";
@ -95,7 +96,7 @@ export function Updatable(props: CommonProps) {
<Flex className={classes(Margins.bottom8, Margins.top8)}>
{isOutdated && (
<Button
size={Button.Sizes.SMALL}
size="small"
disabled={isUpdating || isChecking}
onClick={runWithDispatch(setIsUpdating, async () => {
if (await update()) {
@ -121,7 +122,7 @@ export function Updatable(props: CommonProps) {
</Button>
)}
<Button
size={Button.Sizes.SMALL}
size="small"
disabled={isUpdating || isChecking}
onClick={runWithDispatch(setIsChecking, async () => {
const outdated = await checkForUpdates();

View file

@ -17,6 +17,7 @@
*/
import { useSettings } from "@api/Settings";
import { Button } from "@components/Button";
import { Divider } from "@components/Divider";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
@ -31,7 +32,7 @@ import { relaunch } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { t } from "@utils/translation";
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
import { Alerts, Button, Parser, React, Toasts } from "@webpack/common";
import { Alerts, Parser, React, Toasts } from "@webpack/common";
import gitHash from "~git-hash";
@ -92,7 +93,7 @@ function Updatable(props: CommonProps) {
<>
<Flex className={classes(Margins.bottom8, Margins.top8)}>
{isOutdated && <Button
size={Button.Sizes.SMALL}
size="small"
disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsUpdating, async () => {
if (await update()) {
@ -116,7 +117,7 @@ function Updatable(props: CommonProps) {
Update Now
</Button>}
<Button
size={Button.Sizes.SMALL}
size="small"
disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsChecking, async () => {
const outdated = await checkForUpdates();

View file

@ -7,7 +7,7 @@
import { DonateButton } from "@components/settings/DonateButton";
import BadgeAPI from "@plugins/_api/badges";
import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Button, GuildMemberStore } from "@webpack/common";
import { GuildMemberStore } from "@webpack/common";
export const isDonor = (userId: string) => !!(
BadgeAPI.getDonorBadges(userId)?.length > 0
@ -17,8 +17,6 @@ export const isDonor = (userId: string) => !!(
export function DonateButtonComponent() {
return (
<DonateButton
look={Button.Looks.FILLED}
color={Button.Colors.WHITE}
style={{ marginTop: "1em" }}
/>
);

View file

@ -7,6 +7,7 @@
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
@ -16,7 +17,7 @@ import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import { Button, Select, Slider } from "@webpack/common";
import { Select, Slider } from "@webpack/common";
export function NotificationSection() {
return (

View file

@ -47,3 +47,8 @@
text-align: center;
margin-top: 10px;
}
.vc-vencord-tab-settings-link {
color: var(--text-link);
display: inline-block;
}

View file

@ -8,6 +8,9 @@ import "./VencordTab.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Alert } from "@components/Alert";
import { Button } from "@components/Button";
import { Divider } from "@components/Divider";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
@ -18,12 +21,12 @@ import { DonateButton, InviteButton, openContributorModal, openPluginModal, Sett
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard";
import { gitRemote } from "@shared/vencordUserAgent";
import { DONOR_ROLE_ID, GUILD_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { DONOR_ROLE_ID, GUILD_ID, IS_MAC, IS_WINDOWS, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Margins } from "@utils/margins";
import { identity, isAnyPluginDev } from "@utils/misc";
import { relaunch } from "@utils/native";
import { t, Translate } from "@utils/translation";
import { Button, GuildMemberStore, React, Select, UserStore } from "@webpack/common";
import { GuildMemberStore, React, Select, UserStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges";
import { openNotificationSettingsModal } from "./NotificationSettings";
@ -37,6 +40,8 @@ const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/102653307095587233
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
const cl = classNameFactory("vc-vencord-tab-");
type KeysOfType<Object, Type> = {
[K in keyof Object]: Object[K] extends Type ? K : never;
}[keyof Object];
@ -49,9 +54,7 @@ function EquicordSettings() {
[],
);
const isWindows = navigator.platform.toLowerCase().startsWith("win");
const isMac = navigator.platform.toLowerCase().startsWith("mac");
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
const needsVibrancySettings = IS_DISCORD_DESKTOP && IS_MAC;
const user = UserStore?.getCurrentUser();
@ -77,7 +80,7 @@ function EquicordSettings() {
warning: { enabled: false },
},
!IS_WEB &&
(!IS_DISCORD_DESKTOP || !isWindows
(!IS_DISCORD_DESKTOP || !IS_WINDOWS
? {
key: "frameless",
title: t("vencord.settings.frameless.title"),
@ -96,7 +99,7 @@ function EquicordSettings() {
description: t("vencord.settings.transparent.description"),
restartRequired: true,
warning: {
enabled: isWindows,
enabled: IS_WINDOWS,
message: "Enabling this will prevent you from snapping this window.",
},
},
@ -107,7 +110,7 @@ function EquicordSettings() {
warning: { enabled: false },
},
!IS_WEB &&
isWindows && {
IS_WINDOWS && {
key: "winCtrlQ",
title: t("vencord.settings.winCtrlQ.title"),
restartRequired: true,
@ -206,8 +209,9 @@ function EquicordSettings() {
<Translate i18nKey="vencord.settings.hint">
Hint: You can change the position of this settings section in the
<Button
look={Button.Looks.LINK}
style={{ color: "var(--text-link)", display: "inline-block" }}
variant="none"
size="small"
className={cl("settings-link")}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
>
settings of the Settings plugin
@ -228,9 +232,9 @@ function EquicordSettings() {
s.warning.enabled ? (
<>
{s.description}
<div className="form-switch-warning">
<Alert.Warning className={Margins.top8} style={{ width: "100%" }}>
{s.warning.message}
</div>
</Alert.Warning>
</>
) : (
s.description
@ -332,16 +336,10 @@ function DonateButtonComponent() {
return (
<Flex>
<DonateButton
look={Button.Looks.FILLED}
color={Button.Colors.TRANSPARENT}
style={{ marginTop: "1em" }}
link="https://github.com/sponsors/thororen1234"
/>
equicord={true}
style={{ marginTop: "1em" }} />
<InviteButton
look={Button.Looks.FILLED}
color={Button.Colors.TRANSPARENT}
style={{ marginTop: "1em" }}
/>
style={{ marginTop: "1em" }} />
</Flex>
);
}

View file

@ -1,11 +0,0 @@
.plugin-warning {
font-size: 16px !important;
background-color: var(--info-warning-background) !important;
color: var(--info-warning-foreground) !important;
border: 1px solid var(--info-warning-foreground) !important;
border-radius: 5px !important;
font-weight: bold;
padding: 6px 10px;
text-align: center;
margin-top: 10px;
}

View file

@ -17,13 +17,13 @@ export default definePlugin({
{
find: '"view-profile"',
replacement: {
match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200})return/,
replace: "$1return $self.openUserProfile($2);"
match: /(user:(\i).*?"PRESS_VIEW_PROFILE".{0,200}return)/,
replace: "$1 $self.openUserModal($2);"
},
all: true
},
],
openUserProfile(user: User) {
openUserModal(user: User) {
openUserProfile(user.id);
}
});

View file

@ -110,7 +110,6 @@ export default definePlugin({
name: "DecodeBase64",
description: "Decode base64 content of any message and copy the decoded content.",
authors: [EquicordDevs.ThePirateStoner],
dependencies: ["MessagePopoverAPI"],
settings,
messagePopoverButton: {
icon: DecodeIcon,

View file

@ -163,8 +163,8 @@ export function Boo({ channel }: { channel: Channel; }) {
return;
}
// if exempted, remove from ghost tracking
if (isExempted) {
// if exempted or bot (if setting enabled), remove from ghost tracking
if (isExempted || (settings.store.ignoreBots && lastMessage.author.bot)) {
if (countedChannels.has(id)) {
countedChannels.delete(id);
setBooCount(getBooCount() - 1);
@ -186,7 +186,7 @@ export function Boo({ channel }: { channel: Channel; }) {
}
}, [state.isCurrentUser, state.isDataProcessed, id, lastMessage?.id]);
if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared)
if (!state.isDataProcessed || !currentUserId || !lastMessage || state.isCurrentUser || isChannelExempted(id) || isCleared || (settings.store.ignoreBots && lastMessage.author.bot))
return null;
if (!settings.store.showDmIcons) return null;

View file

@ -84,6 +84,12 @@ export const settings = definePluginSettings({
description: "Something might happen if you ignore someone for too long...",
default: false,
restartNeeded: false
},
ignoreBots: {
type: OptionType.BOOLEAN,
description: "Ignore DMs from bots",
default: true,
restartNeeded: false
}
});

View file

@ -6,7 +6,7 @@
import "./styles.css";
import { addAudioProcessor, AudioProcessor, PreprocessAudioData, removeAudioProcessor } from "@api/AudioPlayer";
import { AudioProcessor, PreprocessAudioData } from "@api/AudioPlayer";
import { get as getFromDataStore } from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
@ -394,15 +394,12 @@ export default definePlugin({
name: "CustomSounds",
description: "Customize Discord's sounds.",
authors: [Devs.ScattrdBlade, Devs.TheKodeToad],
dependencies: ["AudioPlayerAPI"],
settings,
getCustomSoundURL,
startAt: StartAt.Init,
audioProcessor: getCustomSoundURL,
async start() {
console.log("[CustomSounds] Plugin starting...");
addAudioProcessor("CustomSounds", this.getCustomSoundURL);
try {
await preloadDataURIs();
@ -413,7 +410,6 @@ export default definePlugin({
},
stop() {
removeAudioProcessor("CustomSounds");
console.log("[CustomSounds] Plugin stopped");
}
});

File diff suppressed because one or more lines are too long

View file

@ -1,71 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Alerts, ChannelStore, PermissionsBits, PermissionStore } from "@webpack/common";
import filterList from "./constants";
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function warningEmbedNotice(trigger) {
return new Promise<boolean>(resolve => {
Alerts.show({
title: "Hold on!",
body: <div>
<Paragraph>
Your message contains a term on the automod preset list. (Term "{trigger}")
</Paragraph>
<Paragraph>
There is a high chance your message will be blocked and potentially moderated by a server moderator.
</Paragraph>
</div>,
confirmText: "Send Anyway",
cancelText: t("vencord.cancel"),
onConfirm: () => resolve(true),
onCloseCallback: () => setImmediate(() => resolve(false)),
});
});
}
const handleMessage = async (channelId, messageObj) => {
const channel = ChannelStore.getChannel(channelId);
if (channel.isDM()) return { cancel: false };
if (PermissionStore.can(PermissionsBits.ADMINISTRATOR, channel) || PermissionStore.can(PermissionsBits.MANAGE_GUILD, channel)) return { cancel: false };
const escapedStrings = filterList.map(escapeRegex);
const regexString = escapedStrings.join("|");
const regex = new RegExp(`(${regexString})`, "i");
const matches = regex.exec(messageObj.content);
console.log(matches);
if (matches) {
if (!await warningEmbedNotice(matches[0])) {
return { cancel: true };
}
}
return { cancel: false };
};
export default definePlugin({
name: "DontFilterMe",
description: "Warns you if your message contains a term in the automod preset list",
authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"],
start() {
addMessagePreSendListener(handleMessage);
},
stop() {
removeMessagePreSendListener(handleMessage);
}
});

View file

@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -14,12 +12,6 @@ import { PluginButtons } from "./pluginButtons";
import { PluginCards } from "./pluginCards";
const settings = definePluginSettings({
disableCreateDMButton: {
type: OptionType.BOOLEAN,
description: "Disables the create dm button",
restartNeeded: true,
default: false,
},
disableDMContextMenu: {
type: OptionType.BOOLEAN,
description: "Disables the DM list context menu in favor of the x button",
@ -59,15 +51,6 @@ export default definePlugin({
}
]
},
// Disable Giant Create DM Button
{
find: ".createDMButtonContainer,",
predicate: () => settings.store.disableCreateDMButton,
replacement: {
match: /"create-dm"\)/,
replace: "$&&&false"
},
},
// Remove DM Context Menu
{
find: "#{intl::DM_OPTIONS}",

View file

@ -18,13 +18,12 @@
import "./styles.css";
import { addHeaderBarButton, removeHeaderBarButton } from "@api/HeaderBar";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { Popout, useRef, useState } from "@webpack/common";
import type { PropsWithChildren } from "react";
import { renderPopout } from "./menu";
@ -48,7 +47,7 @@ function Icon() {
);
}
function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
function VencordPopoutButton() {
const buttonRef = useRef(null);
const [show, setShow] = useState(false);
@ -66,10 +65,10 @@ function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
{(_, { isShown }) => (
<HeaderBarIcon
ref={buttonRef}
className={`vc-toolbox-btn ${buttonClass}`}
className="vc-toolbox-btn"
onClick={() => setShow(v => !v)}
tooltip={isShown ? null : "Equicord Toolbox"}
icon={() => <Icon />}
icon={Icon}
selected={isShown}
/>
)}
@ -82,27 +81,15 @@ export default definePlugin({
name: "EquicordToolbox",
description: "Adds a button next to the inbox button in the channel header that houses Equicord quick actions",
authors: [Devs.Ven, Devs.AutumnVN],
dependencies: ["HeaderBarAPI"],
settings,
patches: [
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=trailing:.{0,50})\i\.Fragment,\{(?=.+?className:(\i))/,
replace: "$self.TrailingWrapper,{className:$1,"
}
}
],
TrailingWrapper({ children, className }: PropsWithChildren<{ className: string; }>) {
return (
<>
{children}
<ErrorBoundary noop>
<VencordPopoutButton buttonClass={className} />
</ErrorBoundary>
</>
);
start() {
addHeaderBarButton("EquicordToolbox", VencordPopoutButton, 1000);
},
stop() {
removeHeaderBarButton("EquicordToolbox");
}
});

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { removeMessagePopoverButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
@ -154,7 +153,6 @@ export default definePlugin({
enableStyle(styles);
},
stop() {
removeMessagePopoverButton("vc-findreply");
root && root.unmount();
element?.remove();
disableStyle(styles);

View file

@ -4,11 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "@equicordplugins/_misc/styles.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, User, VoiceState } from "@vencord/discord-types";
@ -79,11 +77,11 @@ export default definePlugin({
description: "Follow a friend in voice chat.",
authors: [EquicordDevs.TheArmagan],
settings,
settingsAboutComponent: () => <>
<Paragraph className="plugin-warning">
settingsAboutComponent: () => (
<Alert.Info>
This Plugin is used to follow a Friend/Friends into voice chat(s).
</Paragraph>
</>,
</Alert.Info>
),
flux: {
async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
if (!followedUserInfo) return;

View file

@ -31,8 +31,8 @@ export default definePlugin({
{
find: "#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED}",
replacement: {
match: /if\(.{0,50}\)(return.{0,25}#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED})/,
replace: "if(false)$1",
match: /(\{if\().{0,50}(\)return.{0,25}#{intl::MESSAGE_FORWARDING_NSFW_NOT_ALLOWED})/,
replace: "$1false$2",
}
},
{

View file

@ -1,79 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { React, Tooltip, UserStore } from "@webpack/common";
import { JSX } from "react";
import { openBadgeModal } from "./badgeModal";
import { settings } from "./settings";
import { BadgeCache } from "./types";
import { cl, fetchBadges } from "./utils";
interface BadgeModalItem {
name: string;
rawName: string;
}
export let badgeData;
export const BadgeComponent = ({ name, img }: { name: string, img: string; }) => {
return (
<Tooltip text={name} >
{(tooltipProps: any) => (
<img
{...tooltipProps}
src={img}
className={cl("badge")}
style={{
transform: name.includes("Replugged") ? "scale(0.9)" : null
}}
/>
)}
</Tooltip>
);
};
export const GlobalBadges = ({ userId }: { userId: string; }) => {
const [badges, setBadges] = React.useState<BadgeCache["badges"]>({});
React.useEffect(() => setBadges(fetchBadges(userId) ?? {}), [userId]);
if (!badges) return null;
const globalBadges: JSX.Element[] = [];
const badgeModal: BadgeModalItem[] = [];
Object.keys(badges).forEach(mod => {
if (!badges[mod] || !Array.isArray(badges[mod]) || badges[mod].length === 0) return;
badges[mod].forEach(badge => {
if (!badge || !badge.tooltip || !badge.badge) return;
const prefix = settings.store.showPrefix ? `(${mod})` : "";
const suffix = settings.store.showSuffix ? `(${mod})` : "";
const displayName = `${prefix} ${badge.tooltip} ${suffix}`;
if (mod === "BadgeVault") {
badge.custom = true;
}
globalBadges.push(<BadgeComponent name={displayName} img={badge.badge} />);
badgeModal.push({
name: displayName,
rawName: badge.tooltip,
...badge
});
});
});
badgeData = badgeModal;
return (
<div
className={cl("badges")}
onClick={_ => openBadgeModal(UserStore.getUser(userId))}
>
{globalBadges}
</div>
);
};

View file

@ -13,18 +13,18 @@ import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@u
import { User } from "@vencord/discord-types";
import { React, Tooltip } from "@webpack/common";
import { badgeData } from "./badgeComponent";
import { cl, INVITE_LINK } from "./utils";
import { cl, GlobalBadges, INVITE_LINK } from "./utils";
export const BadgeModalComponent = ({ name, img }: { name: string, img: string; }) => {
export const BadgeModalComponent = ({ key, tooltip, badge }: { key: string, tooltip: string, badge: string; }) => {
return (
<>
<Tooltip text={name} >
<Tooltip text={tooltip} >
{(tooltipProps: any) => (
<img
className={cl("modal-badge")}
key={key}
{...tooltipProps}
src={img}
src={badge}
/>
)}
</Tooltip>
@ -33,8 +33,8 @@ export const BadgeModalComponent = ({ name, img }: { name: string, img: string;
};
export function openBadgeModal(user: User) {
const badgeDataElements = badgeData.map(badge => (
<BadgeModalComponent key={badge.rawName} name={badge.name} img={badge.badge} />
const badgeDataElements = GlobalBadges[user.id].map(badge => (
<BadgeModalComponent key={badge.key} tooltip={badge.tooltip} badge={badge.badge} />
));
openModal(modalprops => (

View file

@ -6,23 +6,19 @@
import "./styles.css";
import { addProfileBadge, BadgePosition, ProfileBadge, removeProfileBadge } from "@api/Badges";
import { BadgePosition, ProfileBadge } from "@api/Badges";
import { Button } from "@components/Button";
import { BadgeContextMenu } from "@plugins/_api/badges";
import { Devs, EquicordDevs } from "@utils/constants";
import { openInviteModal } from "@utils/discord";
import definePlugin from "@utils/types";
import { React } from "@webpack/common";
import { ContextMenuApi, React, Toasts, UserStore } from "@webpack/common";
import { GlobalBadges } from "./badgeComponent";
import { openBadgeModal } from "./badgeModal";
import { settings } from "./settings";
import { cl, fetchBadges, INVITE_LINK } from "./utils";
import { cl, GlobalBadges, INVITE_LINK, loadBadges } from "./utils";
const Badge: ProfileBadge = {
component: b => <GlobalBadges {...b} />,
position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(fetchBadges(userInfo.userId) ?? {}).length,
key: "GlobalBadges"
};
let intervalId: any;
export default definePlugin({
name: "GlobalBadges",
@ -40,6 +36,44 @@ export default definePlugin({
</Button>
</>
),
start: () => addProfileBadge(Badge),
stop: () => removeProfileBadge(Badge),
async start() {
await loadBadges();
clearInterval(intervalId);
intervalId = setInterval(loadBadges, 1000 * 60 * 30);
},
async stop() {
clearInterval(intervalId);
},
toolboxActions: {
async "Refetch Global Badges"() {
await loadBadges();
Toasts.show({
id: Toasts.genId(),
message: "Successfully refetched global badges!",
type: Toasts.Type.SUCCESS
});
}
},
get GlobalBadges() {
return GlobalBadges;
},
getGlobalBadges(userId: string) {
return GlobalBadges[userId]?.map(badge => ({
iconSrc: badge.badge,
description: badge.tooltip,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)"
}
},
onContextMenu(event, badge) {
ContextMenuApi.openContextMenu(event, () => <BadgeContextMenu badge={badge} />);
},
onClick() {
return openBadgeModal(UserStore.getUser(userId));
},
} satisfies ProfileBadge));
}
});

View file

@ -78,12 +78,6 @@ export const settings = definePluginSettings({
default: true,
restartNeeded: false
},
showReplugged: {
type: OptionType.BOOLEAN,
description: "Show Replugged Badges",
default: true,
restartNeeded: false
},
apiUrl: {
type: OptionType.STRING,
description: "API to use",

View file

@ -1,15 +1,3 @@
.vc-global-badges-badges {
align-items: center;
display: flex;
flex-wrap: nowrap;
}
.vc-global-badges-badge {
width: 20px;
height: 20px;
gap: 1px;
}
.vc-global-badges-modal-name {
text-transform: none;
flex-grow: 0;

View file

@ -1,16 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export type CustomBadge = {
tooltip: string;
badge: string;
custom?: boolean;
};
export interface BadgeCache {
badges: { [mod: string]: CustomBadge[]; };
expires: number;
}

View file

@ -4,44 +4,67 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { classNameFactory } from "@api/Styles";
import { settings } from "./settings";
import { BadgeCache } from "./types";
const cache = new Map<string, BadgeCache>();
const EXPIRES = 1000 * 60 * 15;
export const cl = classNameFactory("vc-global-badges-");
export let GlobalBadges = {};
export const INVITE_LINK = "kwHCJPxp8t";
export const fetchBadges = (id: string): BadgeCache["badges"] | undefined => {
const cachedValue = cache.get(id);
if (!cache.has(id) || (cachedValue && cachedValue.expires < Date.now())) {
const services: string[] = [];
if (settings.store.showNekocord) services.push("nekocord");
if (settings.store.showReviewDB) services.push("reviewdb");
if (settings.store.showAero) services.push("aero");
if (settings.store.showAliucord) services.push("aliucord");
if (settings.store.showRa1ncord) services.push("ra1ncord");
if (settings.store.showVelocity) services.push("velocity");
if (settings.store.showEnmity) services.push("enmity");
if (settings.store.showReplugged) services.push("replugged");
if (settings.store.showCustom) services.push("badgevault");
if (services.length === 0) {
cache.set(id, { badges: {}, expires: Date.now() + EXPIRES });
return {};
}
fetch(`${settings.store.apiUrl}${id}?seperated=true&capitalize=true&services=${services.join(",")}`)
.then(res => res.json() as Promise<{ status: number; badges: BadgeCache["badges"]; }>)
.then(body => {
cache.set(id, { badges: body.badges, expires: Date.now() + EXPIRES });
return body.badges;
})
.catch(() => null);
} else if (cachedValue) {
return cachedValue.badges;
}
export const cl = classNameFactory("vc-global-badges-");
export const serviceMap: Record<string, string> = {
nekocord: "Nekocord",
reviewdb: "ReviewDB",
aero: "Aero",
aliucord: "Aliucord",
ra1ncord: "Ra1ncord",
velocity: "Velocity",
enmity: "Enmity",
badgevault: "BadgeVault",
vencord: "Vencord",
equicord: "Equicord",
};
export async function loadBadges() {
const url = settings.store.apiUrl.endsWith("/") ? settings.store.apiUrl + "users" : settings.store.apiUrl + "/users";
const globalBadges = await fetch(url, { cache: "no-cache" }).then(r => r.json());
const filteredUsers: Record<string, typeof globalBadges.users[string]> = {};
for (const key in globalBadges.users) {
filteredUsers[key] = globalBadges.users[key].filter(b => {
const { mod } = b;
if (!mod) return false;
const blockedMods = ["vencord", "equicord"];
if (blockedMods.includes(mod)) return false;
const conditionalMods = {
aero: settings.store.showAero,
velocity: settings.store.showVelocity,
badgevault: settings.store.showCustom,
nekocord: settings.store.showNekocord,
reviewdb: settings.store.showReviewDB,
aliucord: settings.store.showAliucord,
ra1ncord: settings.store.showRa1ncord,
enmity: settings.store.showEnmity
};
if (mod in conditionalMods && !conditionalMods[mod]) return false;
return true;
}).map(b => {
const modFormatted = serviceMap[b.mod];
const prefix = settings.store.showPrefix ? `${modFormatted} - ` : "";
const suffix = settings.store.showSuffix ? ` - ${modFormatted}` : "";
const tooltip = prefix + b.tooltip + suffix;
return {
...b,
key: b.tooltip,
tooltip
};
});
}
GlobalBadges = filteredUsers;
}

View file

@ -1,205 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
import { EquicordDevs } from "@utils/constants";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Message, User } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { Menu, React, TextInput, UserStore } from "@webpack/common";
const UserBoxIcon = findComponentByCodeLazy("0-3-3H5Zm10 6a3");
type NicknamesData = Record<string, string>;
let nicknames: NicknamesData = {};
const settings = definePluginSettings({
enableMessages: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in chat messages.",
default: true,
restartNeeded: true
},
enableMemberList: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in the member list.",
default: true,
restartNeeded: true
},
enableVoiceChannels: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in voice channels.",
default: true,
restartNeeded: true
},
enableTypingIndicator: {
type: OptionType.BOOLEAN,
description: "Show global nicknames in the typing indicator.",
default: true,
restartNeeded: true
}
});
function NicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
const [value, setValue] = React.useState(nicknames[user.id] ?? "");
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Heading tag="h2" style={{ flexGrow: 1, margin: 0 }}>
Change Nickname
</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<div style={{ marginBottom: 20 }}>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
Nickname
</Heading>
<TextInput
value={value}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
</div>
</ModalContent>
<ModalFooter>
<Button
variant="primary"
onClick={async () => {
const trimmed = value.trim();
if (trimmed) {
nicknames[user.id] = trimmed;
} else {
delete nicknames[user.id];
}
await DataStore.set("GlobalNicknames", nicknames);
modalProps.onClose();
}}
>
Save
</Button>
<Button
variant="secondary"
style={{ marginRight: "8px" }}
onClick={modalProps.onClose}
>
{t("vencord.cancel")}
</Button>
</ModalFooter>
</ModalRoot>
);
}
const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => {
if (!user) return;
const currentUser = UserStore.getCurrentUser();
if (!currentUser || user.id === currentUser.id) return;
children.push(
<Menu.MenuSeparator />,
nicknames[user.id] ? (
<Menu.MenuItem
id="remove-nickname"
label="Remove Nickname"
color="danger"
icon={UserBoxIcon}
action={async () => {
delete nicknames[user.id];
await DataStore.set("GlobalNicknames", nicknames);
}}
/>
) : (
<Menu.MenuItem
id="change-nickname"
label="Change Nickname"
icon={UserBoxIcon}
action={() => openModal(props => (
<ErrorBoundary>
<NicknameModal modalProps={props} user={user} />
</ErrorBoundary>
))}
/>
)
);
};
export default definePlugin({
name: "GlobalNicknames",
description: "Set custom nicknames for any user globally.",
authors: [EquicordDevs.Prism],
dependencies: ["ContextMenuAPI"],
settings,
patches: [
{
find: '="SYSTEM_TAG"',
predicate: () => settings.store.enableMessages,
replacement: {
match: /children:(.+?),"data-text":/,
replace: 'children:$self.patchMessageName(arguments[0])??($1),"data-text":'
}
},
{
find: "let{colorRoleName:",
predicate: () => settings.store.enableMemberList,
replacement: {
match: /,name:(null!=\i\?\i:\i),colorStrings/,
replace: ",name:$self.patchName($1,arguments[0].user)||($1),colorStrings"
}
},
{
find: ",connectUserDragSource:",
predicate: () => settings.store.enableVoiceChannels,
replacement: {
match: /nick:(\i),collapsed/,
replace: "nick:$self.patchName($1,arguments[0].user)||$1,collapsed"
}
},
{
find: "#{intl::THREE_USERS_TYPING}",
predicate: () => settings.store.enableTypingIndicator,
replacement: {
match: /(\i\.\i\.getName\(\i\.guild_id,\i\.id),(\i)\)\)/,
replace: "$1,$self.patchTypingName($2)))"
}
}
],
async start() {
const data = await DataStore.get<NicknamesData>("GlobalNicknames");
nicknames = data ?? {};
},
patchMessageName(props: { message: Message; }): string | null {
const userId = props?.message?.author?.id;
return userId ? nicknames[userId] ?? null : null;
},
patchName(_: string, user: User): string | null {
return nicknames[user.id] ?? null;
},
patchTypingName(user: User): User {
const nickname = nicknames[user.id];
if (!nickname) return user;
return { ...user, globalName: nickname, username: nickname } as User;
},
contextMenus: {
"user-context": userContextPatch
}
});

View file

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";

View file

@ -20,8 +20,6 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { DataStore } from "@api/index";
import { removeMessagePopoverButton } from "@api/MessagePopover";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
@ -58,15 +56,13 @@ function ToolBarHeader() {
const iconClasses = findByProps("iconWrapper", "clickable");
return (
<ErrorBoundary noop={true}>
<HeaderBarIcon
tooltip="Holy Notes"
position="bottom"
className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)}
icon={e => Popover(e)}
onClick={() => openModal(props => <NoteModal {...props} />)}
/>
</ErrorBoundary>
<HeaderBarIcon
tooltip="Holy Notes"
position="bottom"
className={classes("vc-note-button", iconClasses.iconWrapper, iconClasses.clickable)}
icon={Popover}
onClick={() => openModal(props => <NoteModal {...props} />)}
/>
);
}
@ -75,17 +71,6 @@ export default definePlugin({
name: "HolyNotes",
description: "Holy Notes allows you to save messages",
authors: [EquicordDevs.Wolfie],
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
patches: [
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.toolbarAction(arguments[0]);$2"
}
}
],
toolboxActions: {
async "Open Notes"() {
@ -97,21 +82,8 @@ export default definePlugin({
"message": messageContextMenuPatch
},
toolbarAction(e) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
renderHeaderBarButton: ToolBarHeader,
e.toolbar = [
<ErrorBoundary noop={true} key={"HolyNotes"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
},
messagePopoverButton: {
icon: NoteButtonPopover,
render(message) {
@ -129,8 +101,4 @@ export default definePlugin({
if (await DataStore.keys(HolyNoteStore).then(keys => !keys.includes("Main"))) return noteHandler.newNoteBook("Main");
if (!noteHandlerCache.has("Main")) await DataStoreToCache();
},
async stop() {
removeMessagePopoverButton("HolyNotes");
}
});

View file

@ -102,7 +102,6 @@ export default definePlugin({
name: "Husk",
description: "Adds Husk button (check settings to change used emoji)",
authors: [Devs.nin0dev],
dependencies: ["MessagePopoverAPI"],
settings,
messagePopoverButton: {
icon: Husk,

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { HeadingPrimary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { openUserProfile } from "@utils/discord";
import { Avatar, Clickable, React, TextInput, Tooltip, } from "@webpack/common";
@ -44,11 +44,9 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users
.map(({ name, users }) => (
<aside key={name}>
<div className={cl("header-container")}>
<BaseText>{name.toUpperCase()}</BaseText>
<HeadingPrimary className={cl("header-name")}>{name}</HeadingPrimary>
<div className={cl("header-btns")}>
<Flex>
{users.map(u => <UserRow key={u.id} user={u} />)}
</Flex>
{users.map(u => <UserRow key={u.id} user={u} />)}
</div>
</div>
</aside>
@ -59,14 +57,14 @@ function UsersCollectionRows({ usersCollection }: { usersCollection: Data["users
function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?: boolean; }) {
return (
<Flex key={user.id} className={cl("user-row")}>
<span className={cl("user")}>
<Flex>
<Clickable onClick={() => openUserProfile(user.id)}>
<span className={cl("user-avatar")}>
<Avatar src={user.iconURL} size="SIZE_24" />
</span>
</Clickable>
<div key={user.id} className={cl("user-row")}>
<div className={cl("user")}>
<Clickable onClick={() => openUserProfile(user.id)}>
<span className={cl("user-avatar")}>
<Avatar src={user.iconURL} size="SIZE_24" />
</span>
</Clickable>
<div className={cl("user-tooltip")}>
<Tooltip text={tooltipText(user)}>
{props =>
<Paragraph {...props} className={cl("user-username")}>
@ -74,15 +72,14 @@ function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?:
</Paragraph>
}
</Tooltip>
</Flex>
</span>
<span className={cl("user-id")}>
<Paragraph>
{user.id}
</Paragraph>
</span>
</Flex>
<span className={cl("user-id")}>
<Paragraph>
{user.id}
</Paragraph>
</span>
</div>
</div>
</div>
);
}
@ -94,10 +91,10 @@ function SearchElement({ usersCollection }: { usersCollection: Data["usersCollec
<section className={cl("search")}>
<TextInput placeholder="Filter by tag, username" name="Filter" onChange={setCurrent} />
{current && (
<Flex className={cl("search-user")}>
<div className={cl("search-user")}>
{list.filter(user => user.tag.includes(current) || user.username.includes(current))
.map(user => <UserRow key={user.id} user={user} allowOwner={false} />)}
</Flex>
</div>
)}
</section>
);
@ -112,9 +109,9 @@ export function DataUI({ plugin, usersCollection }: { plugin: any; usersCollecti
<SectionDescription />
<SearchElement usersCollection={usersCollection} />
<Flex className={cl("rows")}>
<div className={cl("rows")}>
<UsersCollectionRows usersCollection={usersCollection} />
</Flex>
</div>
</main>
);
}

View file

@ -5,8 +5,15 @@
flex-direction: column;
}
.vc-i-remember-you-header-name {
margin-top: 16px;
}
.vc-i-remember-you-header-btns {
gap: 8px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 350px));
gap: 10px;
margin-top: 8px;
}
.vc-i-remember-you-header {
@ -29,49 +36,38 @@
.vc-i-remember-you-user-avatar {
cursor: pointer;
margin-right: 30px;
margin-right: 0;
}
.vc-i-remember-you-user-row {
margin: 0;
width: 100%;
flex-wrap: wrap;
align-items: center;
display: flex;
flex-direction: column;
gap: 6px;
padding: 4px;
align-items: flex-start;
}
.vc-i-remember-you-user-id {
height: min-content;
align-self: flex-start;
opacity: 0.75;
height: min-content;
}
.vc-i-remember-you-user {
width: 500px;
gap: 250px;
display: flex;
flex-direction: row;
align-items: center;
overflow-wrap: break-word;
margin-top: 5px;
gap: 8px;
width: 100%;
margin-top: 0;
}
.vc-i-remember-you-user-username {
margin-top: 2px;
}
.vc-i-remember-you-buttons {
margin-top: 10px;
}
.vc-i-remember-you-buttons-save,
.vc-i-remember-you-buttons-cancel {
margin-left: 490px;
margin-top: 5px;
margin-bottom: 10px;
}
.vc-i-remember-you-buttons-cancel {
margin-left: 12px;
}
.vc-i-remember-you-buttons-reset {
cursor: pointer;
margin-left: 12px;
.vc-i-remember-you-user-tooltip {
display: flex;
flex-direction: column;
align-items: flex-start;
}

View file

@ -79,24 +79,39 @@ const IngtoninatorButton: ChatBarButtonFactory = ({ isMainChat }) => {
onClick={toggle}
>
{isEnabled ? (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<path transform="translate(351 -153)" fill="currentcolor" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
</svg>
enabledIcon()
) : (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<path transform="translate(351 -153)" fill="var(--status-danger)" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
</svg>
disabledIcon()
)}
</ChatBarButton>
);
};
function enabledIcon() {
return (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<path transform="translate(351 -153)" fill="currentcolor" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
</svg>
);
}
function disabledIcon() {
return (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<path transform="translate(351 -153)" fill="var(--status-danger)" d="M-177.7,334.5c6.3-2.3,12.6-5.2,19.8-8.6c31.9-16.4,51.7-41.7,51.7-41.7s-32.5,0.6-64.4,17 c-4,1.7-7.5,4-10.9,5.7c5.7-7.5,12.1-16.4,18.7-25c25-37.1,31.3-77.3,31.3-77.3s-34.8,21-59.2,58.6c-5.2,7.5-9.8,14.9-13.8,22.7 c1.1-10.3,1.1-22.1,1.1-33.6c0-50-19.8-91.1-19.8-91.1s-19.8,40.5-19.8,91.1c0,12.1,0.6,23.3,1.1,33.6c-4-7.5-8.6-14.9-13.8-22.7 c-25-37.1-59.2-58.6-59.2-58.6s6.3,40,31.3,77.3c6.3,9.2,12.1,17.5,18.7,25c-3.4-2.3-7.5-4-10.9-5.7c-31.9-16.4-64.4-17-64.4-17 s19.8,25.6,51.7,41.7c6.9,3.4,13.2,6.3,19.8,8.6c-4,0.6-8,1.1-12.1,2.3c-30.5,6.4-53.2,23.9-53.2,23.9s27.3,7.5,58.6,1.1 c9.8-2.3,19.8-4.6,27.3-7.5c-1.1,1.1,15.8-8.6,21.6-14.4v60.4h8.6v-61.8c6.3,6.3,22.7,16.4,22.1,14.9c8,2.9,17.5,5.2,27.3,7.5 c30.8,6.3,58.6-1.1,58.6-1.1s-22.1-17.5-53.4-23.8C-169.6,335.7-173.7,335.1-177.7,334.5z" />
</svg>
);
}
export default definePlugin({
name: "Ingtoninator",
description: "Suffixes 'ington' to a random word in your message",
authors: [EquicordDevs.zyqunix],
settings,
renderChatBarButton: IngtoninatorButton,
chatBarButton: {
icon: disabledIcon,
render: IngtoninatorButton
},
start() {
addMessagePreSendListener(handleMessage);
},

View file

@ -65,7 +65,7 @@ function Indicator() {
}
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
const ChatBarRender: ChatBarButtonFactory = ({ isMainChat }) => {
if (!isMainChat) return null;
return (
@ -76,20 +76,26 @@ const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
"aria-haspopup": "dialog",
}}
>
<svg
aria-hidden
role="img"
width="20"
height="20"
viewBox={"0 0 64 64"}
style={{ scale: "1.39", translate: "0 -1px" }}
>
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
{ChatBarIcon()}
</ChatBarButton>
);
};
function ChatBarIcon() {
return (
<svg
aria-hidden
role="img"
width="20"
height="20"
viewBox={"0 0 64 64"}
style={{ scale: "1.39", translate: "0 -1px" }}
>
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
);
}
const settings = definePluginSettings({
savedPasswords: {
type: OptionType.STRING,
@ -149,6 +155,11 @@ export default definePlugin({
reporterTestable: ReporterTestable.Patches,
settings,
chatBarButton: {
icon: ChatBarIcon,
render: ChatBarRender
},
patches: [
{
// Indicator
@ -165,7 +176,6 @@ export default definePlugin({
const { default: StegCloak } = await getStegCloak();
steggo = new StegCloak(true, false);
},
renderChatBarButton: ChatBarIcon,
messagePopoverButton: {
icon: PopOverIcon,
render(message) {

View file

@ -72,14 +72,7 @@ const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => {
alignItems: "center",
gap: "4px"
}}>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
</svg>
{FetchTimeIcon()}
<span style={{
fontSize: "12px",
color: iconColor,
@ -92,6 +85,19 @@ const FetchTimeButton: ChatBarButtonFactory = ({ isMainChat }) => {
);
};
function FetchTimeIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
</svg>
);
}
function formatTimeAgo(timestamp: Date): string {
const now = new Date();
const diff = now.getTime() - timestamp.getTime();
@ -145,6 +151,11 @@ export default definePlugin({
authors: [EquicordDevs.GroupXyz],
settings,
chatBarButton: {
icon: FetchTimeIcon,
render: FetchTimeButton
},
start() {
FluxDispatcher.subscribe("CHANNEL_SELECT", handleChannelSelect);
FluxDispatcher.subscribe("LOAD_MESSAGES_SUCCESS", handleMessageLoad);
@ -164,7 +175,5 @@ export default definePlugin({
currentFetch = null;
channelTimings.clear();
currentChannelId = null;
},
renderChatBarButton: FetchTimeButton,
}
});

View file

@ -21,28 +21,7 @@ import { findComponentByCodeLazy } from "@webpack";
import { openLogModal } from "./LogsModal";
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
export function OpenLogsIcon() {
return (
<svg
stroke="currentColor"
fill="none"
strokeWidth="0"
viewBox="0 0 15 15"
height={24}
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M3 2.5C3 2.22386 3.22386 2 3.5 2H9.08579C9.21839 2 9.34557 2.05268 9.43934 2.14645L11.8536 4.56066C11.9473 4.65443 12 4.78161 12 4.91421V12.5C12 12.7761 11.7761 13 11.5 13H3.5C3.22386 13 3 12.7761 3 12.5V2.5ZM3.5 1C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H11.5C12.3284 14 13 13.3284 13 12.5V4.91421C13 4.51639 12.842 4.13486 12.5607 3.85355L10.1464 1.43934C9.86514 1.15804 9.48361 1 9.08579 1H3.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5C4 4.77614 4.22386 5 4.5 5H7.5C7.77614 5 8 4.77614 8 4.5C8 4.22386 7.77614 4 7.5 4H4.5ZM4.5 7C4.22386 7 4 7.22386 4 7.5C4 7.77614 4.22386 8 4.5 8H10.5C10.7761 8 11 7.77614 11 7.5C11 7.22386 10.7761 7 10.5 7H4.5ZM4.5 10C4.22386 10 4 10.2239 4 10.5C4 10.7761 4.22386 11 4.5 11H10.5C10.7761 11 11 10.7761 11 10.5C11 10.2239 10.7761 10 10.5 10H4.5Z"
>
</path>
</svg>
);
}
const Icon = findComponentByCodeLazy("0-1.27-.97l-2.5.7a3");
export function OpenLogsButton() {
return (
@ -50,7 +29,7 @@ export function OpenLogsButton() {
className="vc-log-toolbox-btn"
onClick={() => openLogModal()}
tooltip={"Open Logs"}
icon={OpenLogsIcon}
icon={Icon}
/>
);
}

View file

@ -8,12 +8,11 @@ export const Native = getNative();
import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, MessageStore, React, UserStore } from "@webpack/common";
import { FluxDispatcher, MessageStore, UserStore } from "@webpack/common";
import { OpenLogsButton } from "./components/LogsButton";
import { openLogModal } from "./components/LogsModal";
@ -256,15 +255,6 @@ export default definePlugin({
replace: "deleted:$self.getDeleted(...arguments), editHistory:$self.getEdited(...arguments),"
}
},
{
find: ".controlButtonWrapper,",
predicate: () => settings.store.ShowLogsButton,
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
},
{
find: "childrenMessageContent:null",
replacement: {
@ -310,21 +300,9 @@ export default definePlugin({
openLogModal();
}
},
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<OpenLogsButton />
</ErrorBoundary>,
e.toolbar,
];
renderHeaderBarButton() {
if (!settings.store.ShowLogsButton) return null;
return OpenLogsButton();
},
processMessageFetch,

View file

@ -50,12 +50,12 @@ export default definePlugin({
find: ".gifts)",
replacement: [
{
match: /(?<="submit"\)\}\);)(?=.{0,50}null!=(\i))/,
replace: '$1["stickers+"]=3;'
match: /(?<=(,!\i&&\(null==\(\i=\i\.stickers\)\?void 0.*?\i\.push\(\{).{0,15}node:(.{0,45})},"sticker"\)\}\))/,
replace: "$1key:\"stickers+\",node:$2,stickersType:\"stickers+\"},\"stickers+\")})"
},
{
match: /(?<=,.{0,5}\(null==\(\i=\i\.stickers\)\?void 0.*?(\i)\.push\(\{.{0,15}node:(.{0,15}{disabled:\i,type:\i)},"sticker"\)\}\))/,
replace: ",arguments[0].type?.submit?.button&&$1.push({key:\"stickers+\",node:$2,stickersType:\"stickers+\"},\"stickers+\")})"
match: /(?<="submit"\)\}\);)(?=.{0,50}null!=(\i))/,
replace: '$1["stickers+"]=3;'
}
]
},

View file

@ -35,7 +35,6 @@ export default definePlugin({
name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings,
patches: [
// Make discord actually use our tags

View file

@ -16,9 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "@equicordplugins/_misc/styles.css";
import { Paragraph } from "@components/Paragraph";
import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -26,11 +24,11 @@ export default definePlugin({
name: "NeverPausePreviews",
description: "Prevents in-call/PiP previews (screenshare, streams, etc) from pausing even if the client loses focus",
authors: [EquicordDevs.vappstar],
settingsAboutComponent: () => <>
<Paragraph className="plugin-warning">
settingsAboutComponent: () => (
<Alert.Warning>
This plugin will cause discord to use more resources than normal
</Paragraph>
</>,
</Alert.Warning>
),
patches: [
{
find: "streamerPaused()",

View file

@ -0,0 +1,23 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoAppsAllowed",
description: "returns the bot's tag :skulk:",
authors: [EquicordDevs.meowabyte],
patches: [
{
find: '"#{intl::APP_TAG::hash}":',
replacement: {
match: /(#{intl::APP_TAG::hash}":\[").*?("\])/,
replace: "$1BOT$2"
}
}
]
});

View file

@ -4,23 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoBulletPoints",
description: "Stops you from typing markdown bullet points (stinky)",
authors: [Devs.Samwich],
dependencies: ["MessageEventsAPI"],
start() {
this.preSend = addMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
},
stop() {
this.preSend = removeMessagePreSendListener((channelId, msg) => {
msg.content = textProcessing(msg.content);
});
onBeforeMessageSend(channelId, msg) {
msg.content = textProcessing(msg.content);
},
});

View file

@ -4,10 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "@equicordplugins/_misc/styles.css";
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { UserStore } from "@webpack/common";
@ -51,11 +49,11 @@ export default definePlugin({
name: "PlatformSpoofer",
description: "Spoof what platform or device you're on",
authors: [EquicordDevs.Drag],
settingsAboutComponent: () => <>
<Paragraph className="plugin-warning">
settingsAboutComponent: () => (
<Alert.Warning>
We can't guarantee this plugin won't get you warned or banned.
</Paragraph>
</>,
</Alert.Warning>
),
settings: settings,
patches: [
{

View file

@ -5,9 +5,7 @@
*/
import {
addMessagePreSendListener,
MessageSendListener,
removeMessagePreSendListener,
} from "@api/MessageEvents";
import {
definePluginSettings,
@ -67,9 +65,7 @@ export default definePlugin({
name: "PolishWording",
description: "Tweaks your messages to make them look nicer and have better grammar. See settings",
authors: [Devs.Samwich, EquicordDevs.WKoA],
dependencies: ["MessageEventsAPI"],
start: () => addMessagePreSendListener(presendObject),
stop: () => removeMessagePreSendListener(presendObject),
onBeforeMessageSend: presendObject,
settings,
});

View file

@ -16,17 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "@equicordplugins/_misc/styles.css";
import "./style.css";
import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/Settings";
import { ErrorBoundary } from "@components/index";
import { Devs } from "@utils/constants";
import { getTheme, Theme } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, Tooltip, UserStore } from "@webpack/common";
import { ChannelStore, FluxDispatcher, GuildChannelStore, NavigationRouter, RestAPI, UserStore } from "@webpack/common";
const QuestIcon = findComponentByCodeLazy("10.47a.76.76");
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
@ -38,16 +36,13 @@ let questIdCheck = 0;
function ToolBarHeader() {
return (
<ErrorBoundary noop={true}>
<HeaderBarIcon
tooltip="Complete Quest"
position="bottom"
className="vc-quest-completer"
icon={QuestIcon}
onClick={openCompleteQuestUI}
>
</HeaderBarIcon>
</ErrorBoundary>
<HeaderBarIcon
tooltip="Complete Quest"
position="bottom"
className="vc-quest-completer"
icon={QuestIcon}
onClick={openCompleteQuestUI}
/>
);
}
@ -213,11 +208,6 @@ async function openCompleteQuestUI() {
}
const settings = definePluginSettings({
useNavBar: {
description: "Move quest button down to the server nav bar",
type: OptionType.BOOLEAN,
default: false,
},
disableNotifications: {
description: "Disable notifications when no quests are available or when a quest is completed",
type: OptionType.BOOLEAN,
@ -238,22 +228,6 @@ export default definePlugin({
replace: "onClick:()=>$self.mobileQuestPatch($1)"
},
},
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /"HELP".{0,100}className:\i\}\)(?=\])/,
replace: "$&,$self.renderTitleBar()"
},
predicate: () => !settings.store.useNavBar
},
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,200}mobileToolbar)/,
replace: "$1$self.renderNavBar(arguments[0]);$2"
},
predicate: () => settings.store.useNavBar
}
],
mobileQuestPatch(questId) {
if (questId === questIdCheck) return;
@ -265,37 +239,5 @@ export default definePlugin({
}
});
},
renderTitleBar() {
return (
<ErrorBoundary noop>
<Tooltip text="Complete Quest">
{tooltipProps => (
<Button style={{ backgroundColor: "transparent", border: "none" }}
{...tooltipProps}
size={Button.Sizes.SMALL}
className={"vc-quest-completer-icon"}
onClick={openCompleteQuestUI}
>
<QuestIcon width={20} height={20} />
</Button>
)}
</Tooltip>
</ErrorBoundary>
);
},
renderNavBar(e) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<ToolBarHeader />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"QuestCompleter"}>
<ToolBarHeader />
</ErrorBoundary>,
e.toolbar,
];
}
renderHeaderBarButton: ToolBarHeader
});

View file

@ -4,20 +4,18 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "@equicordplugins/_misc/styles.css";
import { Paragraph } from "@components/Paragraph";
import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "QuestFocused",
description: "Prevent the quests player from pausing and possibly skip it all together.",
settingsAboutComponent: () => <>
<Paragraph className="plugin-warning">
settingsAboutComponent: () => (
<Alert.Warning>
You might need to spam left mouse button on the video to skip it.
</Paragraph>
</>,
</Alert.Warning>
),
authors: [EquicordDevs.secp192k1],
patches: [
// Block pausing

View file

@ -1073,7 +1073,7 @@ export default definePlugin({
},
{
// Adds a maxDigits prop to the LowerBadge component which allows for not truncating, or for truncating at a specific threshold.
find: "STATUS_DANGER,disableColor",
find: '"renderBadgeCount"])',
group: true,
replacement: [
{
@ -1251,7 +1251,7 @@ export default definePlugin({
},
{
// Add the trigger to the memo for rerendering the progress label.
match: /(\i.intl.string\(\i.\i#{intl::QUESTS_SEE_CODE}\)}\)}},\[)/,
match: /(\i\.intl\.string\(\i\.\i#{intl::QUESTS_SEE_CODE}\)\}\)\}\},\[|\)\}\}\},\[)/,
replace: "$1questRerenderTrigger,"
},
{

View file

@ -14,8 +14,8 @@ import { formatLowerBadge } from "./misc";
// GuildlessServerListItem's built-in pill does not support unread state.
export const GuildlessServerListItemComponent = findComponentByCodeLazy("tooltip:", "lowerBadgeSize:");
export const GuildedServerListItemPillComponent = findComponentByCodeLazy('"pill":"empty"');
export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,disableColor:");
export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("STATUS_DANGER,shape:", "iconBadge,");
export const ServerListItemLowerBadgeComponent = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"');
export const ServerListItemUpperBadgeComponent = findComponentByCodeLazy("iconBadge", ".ROUND,disableColor");
export const RadioGroup = findComponentByCodeLazy(',["label","description"');
export const QuestTile = findComponentByCodeLazy(".rowIndex,trackGuildAndChannelMetadata");

View file

@ -34,9 +34,11 @@ export default definePlugin({
name: "SekaiStickers",
description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka",
authors: [Devs.MaiKokain],
dependencies: ["ChatInputButtonAPI"],
settings,
renderChatBarButton: SekaiStickerChatButton,
chatBarButton: {
icon: kanadeSvg,
render: SekaiStickerChatButton
},
async start() {
const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/47a2ca33b8cb35f59800e8faad48980e4ce5ea71/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }];
if (!IS_FONTS_LOADED) {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { Devs, EquicordDevs } from "@utils/constants";
import { isEquicordPluginDev, isPluginDev } from "@utils/misc";
import definePlugin from "@utils/types";
@ -142,12 +141,8 @@ export default definePlugin({
name: "ShowBadgesInChat",
authors: [Devs.Inbestigator, EquicordDevs.KrystalSkull],
description: "Shows the message author's badges beside their name in chat.",
dependencies: ["MessageDecorationsAPI"],
settings,
start: () => {
addMessageDecoration("vc-show-badges-in-chat", props => props.message?.author ? <ChatBadges author={props.message.author} /> : null);
},
stop: () => {
removeMessageDecoration("vc-show-badges-in-chat");
renderMessageDecoration(props) {
return props.message?.author ? <ChatBadges author={props.message.author} /> : null;
}
});

View file

@ -11,11 +11,15 @@ export default definePlugin({
name: "ShowResourceChannels",
description: "shows the channels hidden behind the server resources in the channel list",
authors: [EquicordDevs.VillainsRule],
patches: [{
find: '"should_show_in_recents"',
replacement: [{
match: /\i\.hideResourceChannels&&/,
replace: "false&&"
}]
}]
patches: [
{
find: ".GUILD_DIRECTORY:null",
replacement: [
{
match: /\i\.hideResourceChannels&&/,
replace: "false&&"
}
]
}
]
});

View file

@ -7,7 +7,6 @@
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -70,13 +69,13 @@ const SignatureToggle: ChatBarButtonFactory = ({ isMainChat }) => {
);
};
const handleMessage = ((channelId, msg) => {
if (!settings.store.isEnabled) {
msg.content = msg.content;
} else {
msg.content = textProcessing(msg.content);
}
});
function SignatureIcon() {
return (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21.333">
<path fill="currentColor" d="M2 4.621a.5.5 0 0 1 .854-.353l6.01 6.01c.126.126.17.31.15.487a2 2 0 1 0 1.751-1.751a.59.59 0 0 1-.487-.15l-6.01-6.01A.5.5 0 0 1 4.62 2H11a9 9 0 0 1 8.468 12.054l2.24 2.239a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.415 0l-2.239-2.239A9 9 0 0 1 2 11z" />
</svg>
);
}
const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]);
@ -102,17 +101,23 @@ export default definePlugin({
name: "Signature",
description: "Automated fingerprint/end text",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.KrystalSkull],
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
renderChatBarButton: SignatureToggle,
onBeforeMessageSend(channelId, msg) {
if (!settings.store.isEnabled) {
msg.content = msg.content;
} else {
msg.content = textProcessing(msg.content);
}
},
start: () => {
if (settings.store.isEnabled) true;
addMessagePreSendListener(handleMessage);
},
stop: () => {
if (settings.store.isEnabled) false;
removeMessagePreSendListener(handleMessage);
},
chatBarButton: {
icon: SignatureIcon,
render: SignatureToggle
},
settings,

View file

@ -6,7 +6,6 @@
import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
@ -143,15 +142,7 @@ export default definePlugin({
authors: [EquicordDevs.sliwka],
settings,
dependencies: ["AudioPlayerAPI"],
patches: [
{
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,450}mobileToolbar)/,
replace: "$1$self.addIconToToolBar(arguments[0]);$2"
}
}
],
renderHeaderBarButton: SoggyButton,
start() {
assignBoop(settings.store.boopLink, settings.store.boopVolume * 100);
@ -162,21 +153,4 @@ export default definePlugin({
boopSound?.delete();
song?.delete();
},
// taken from message logger lol
addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<SoggyButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"MessageLoggerEnhanced"} >
<SoggyButton />
</ErrorBoundary>,
e.toolbar,
];
},
});

View file

@ -4,14 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { CharBarButton, ChatBarIcon } from "./components/Icons";
import { OpenSBLogsButton } from "./components/SoundBoardLog";
import settings from "./settings";
import { updateLoggedSounds } from "./store";
@ -22,33 +19,9 @@ export default definePlugin({
name: "SoundBoardLogger",
authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.amy, Devs.thororen],
description: "Logs all soundboards that are played in a voice chat and allows you to download them",
dependencies: ["AudioPlayerAPI", "ChatInputButtonAPI"],
patches: [
{
predicate: () => settings.store.IconLocation === "toolbar",
find: ".controlButtonWrapper,",
replacement: {
match: /(function \i\(\i\){)(.{1,500}toolbar.{1,500}mobileToolbar)/,
replace: "$1$self.addSBIconToToolBar(arguments[0]);$2"
}
}
],
dependencies: ["AudioPlayerAPI"],
settings,
addSBIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.unshift(
<ErrorBoundary noop={true}>
<OpenSBLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true} key={"SoundBoardLogger"} >
<OpenSBLogsButton />
</ErrorBoundary>,
e.toolbar,
];
},
renderHeaderBarButton: OpenSBLogsButton,
start() {
enableStyle(styles);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => {
@ -56,10 +29,8 @@ export default definePlugin({
await updateLoggedSounds(sound);
getListeners().forEach(cb => cb());
});
if (settings.store.IconLocation === "chat") addChatBarButton("vc-soundlog-button", CharBarButton, ChatBarIcon);
},
stop() {
disableStyle(styles);
if (settings.store.IconLocation === "chat") removeChatBarButton("vc-soundlog-button");
}
});

View file

@ -85,15 +85,6 @@ const settings = definePluginSettings({
default: 0.5,
markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
},
IconLocation: {
description: "choose where to show the SoundBoard Log icon (requires restart)",
type: OptionType.SELECT,
options: [
{ label: "Toolbar", value: "toolbar", default: true },
{ label: "Chat input", value: "chat" }
],
restartNeeded: true
},
});
export default settings;

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { addMessagePreSendListener, MessageSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { MessageSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { getCurrentChannel, sendMessage } from "@utils/discord";
@ -117,16 +117,11 @@ export default definePlugin({
name: "SplitLargeMessages",
description: "Splits large messages into multiple to fit Discord's message limit.",
authors: [EquicordDevs.Reycko],
dependencies: ["MessageEventsAPI"],
settings,
onBeforeMessageSend: listener,
start() {
if (settings.store.maxLength === 0) maxLength = autoMaxLength();
addMessagePreSendListener(listener);
},
stop() {
removeMessagePreSendListener(listener);
},
patches: [

View file

@ -54,10 +54,21 @@ const ReverseMessageToggle: ChatBarButtonFactory = ({ isMainChat }) => {
);
};
function ReverseMessageIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20">
<path fill={"currentColor"} d="M482-160q-134 0-228-93t-94-227v-7l-36 36q-11 11-28 11t-28-11q-11-11-11-28t11-28l104-104q12-12 28-12t28 12l104 104q11 11 11 28t-11 28q-11 11-28 11t-28-11l-36-36v7q0 100 70.5 170T482-240q16 0 31.5-2t30.5-7q17-5 32 1t23 21q8 16 1.5 31.5T577-175q-23 8-47 11.5t-48 3.5Zm-4-560q-16 0-31.5 2t-30.5 7q-17 5-32.5-1T360-733q-8-15-1.5-30.5T381-784q24-8 48-12t49-4q134 0 228 93t94 227v7l36-36q11-11 28-11t28 11q11 11 11 28t-11 28L788-349q-12 12-28 12t-28-12L628-453q-11-11-11-28t11-28q11-11 28-11t28 11l36 36v-7q0-100-70.5-170T478-720Z" />
</svg>
);
}
export default definePlugin({
name: "TalkInReverse",
authors: [EquicordDevs.Tolgchu],
description: "Reverses the message content before sending it.",
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
renderChatBarButton: ReverseMessageToggle,
dependencies: ["MessageEventsAPI"],
chatBarButton: {
icon: ReverseMessageIcon,
render: ReverseMessageToggle
}
});

View file

@ -46,7 +46,6 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
export default definePlugin({
name: "Translate+",
description: "Vencord's translate plugin but with support for artistic languages!",
dependencies: ["MessageAccessoriesAPI"],
authors: [Devs.Ven, EquicordDevs.Prince527],
settings,
contextMenus: {

View file

@ -5,7 +5,7 @@
*/
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { Alert } from "@components/Alert";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
@ -41,9 +41,9 @@ export default definePlugin({
description: "Mention any user, regardless of channel access.",
settings,
settingsAboutComponent: () => (
<Paragraph className="plugin-warning">
<Alert.Warning>
Using Global Mention can cause performance issues and show an absurd amount of users in the autocomplete.
</Paragraph>
</Alert.Warning>
),
patches: [

View file

@ -14,7 +14,7 @@ import { ReadStateStore, useStateFromStores } from "@webpack/common";
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore");
const NumberBadge = findComponentByCodeLazy("numberBadge", ".STATUS_DANGER,");
const NumberBadge = findComponentByCodeLazy("numberBadge", '"renderBadgeCount"');
const settings = definePluginSettings({
showOnMutedChannels: {

View file

@ -1,96 +0,0 @@
/*
* 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 ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Tooltip, useRef, useState } from "@webpack/common";
const settings = definePluginSettings(
{
preservePitch: {
type: OptionType.BOOLEAN,
description: "Should pitch be preserved when changing speed?",
default: false,
restartNeeded: true
},
});
export default definePlugin({
name: "VideoSpeed",
description: "Allows you to change the speed of videos",
authors: [Devs.Samwich],
settings,
patches: [
{
find: /\.VIDEO\?\i\.videoControls:/,
replacement: {
match: /children:\[this\.renderPlayIcon\(\),.{0,200}\.setDurationRef}\),/,
replace: "$&$self.SpeedButton(),"
},
},
],
SpeedButton: ErrorBoundary.wrap(() => {
const elementRef = useRef<HTMLDivElement>(null);
const [speed, setSpeed] = useState(1);
const speedPaths =
{
1.5: "M320 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m-115.5 73.3c-5.4-2.5-11.7-1.9-16.4 1.7l-40.9 30.7c-14.1 10.6-34.2 7.7-44.8-6.4s-7.7-34.2 6.4-44.8l40.9-30.7c23.7-17.8 55.3-21 82.1-8.4l90.4 42.5c29.1 13.7 36.8 51.6 15.2 75.5L299.1 224h97.4c30.3 0 53 27.7 47.1 57.4l-28.2 140.9c-3.5 17.3-20.3 28.6-37.7 25.1s-28.6-20.3-25.1-37.7L377 288h-70.3c8.6 19.6 13.3 41.2 13.3 64c0 88.4-71.6 160-160 160S0 440.4 0 352s71.6-160 160-160c11.1 0 22 1.1 32.4 3.3l54.2-54.2zM160 448a96 96 0 1 0 0-192a96 96 0 1 0 0 192",
1.25: "M320 48a48 48 0 1 0-96 0a48 48 0 1 0 96 0M125.7 175.5c9.9-9.9 23.4-15.5 37.5-15.5c1.9 0 3.8.1 5.6.3L137.6 254c-9.3 28 1.7 58.8 26.8 74.5l86.2 53.9l-25.4 88.8c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l28.7-100.4c5.9-20.6-2.6-42.6-20.7-53.9L238 299l30.9-82.4l5.1 12.3c15 35.8 49.9 59.1 88.7 59.1H384c17.7 0 32-14.3 32-32s-14.3-32-32-32h-21.3c-12.9 0-24.6-7.8-29.5-19.7l-6.3-15c-14.6-35.1-44.1-61.9-80.5-73.1l-48.7-15C186.6 97.8 175 96 163.3 96c-31 0-60.8 12.3-82.7 34.3l-23.2 23.1c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l23.1-23.1zM91.2 352H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h69.6c19 0 36.2-11.2 43.9-28.5l11.5-25.9l-9.5-6a95.394 95.394 0 0 1-37.9-44.9z",
1: "M112 48a48 48 0 1 1 96 0a48 48 0 1 1-96 0m40 304v128c0 17.7-14.3 32-32 32s-32-14.3-32-32V256.9l-28.6 47.6c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6h29.7c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32V352z",
0.5: "M368 32c41.7 0 75.9 31.8 79.7 72.5l85.6 26.3c25.4 7.8 42.8 31.3 42.8 57.9c0 21.8-11.7 41.9-30.7 52.7l-144.6 82.1l92.5 92.5H544c17.7 0 32 14.3 32 32s-14.3 32-32 32h-64c-8.5 0-16.6-3.4-22.6-9.4L346.9 360.2c11.7-36 3.2-77.1-25.4-105.7c-40.6-40.6-106.3-40.6-146.9-.1l-73.6 70c-6.4 6.1-6.7 16.2-.6 22.6s16.2 6.6 22.6.6l73.8-70.2l.1-.1l.1-.1c3.5-3.5 7.3-6.6 11.3-9.2c27.9-18.5 65.9-15.4 90.5 9.2c24.7 24.7 27.7 62.9 9 90.9c-2.6 3.8-5.6 7.5-9 10.9l-37 37H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H64c-35.3 0-64-28.7-64-64C0 249.6 127 112.9 289.3 97.5C296.2 60.2 328.8 32 368 32m0 104a24 24 0 1 0 0-48a24 24 0 1 0 0 48"
};
return <div ref={elementRef}>
<Tooltip text={`Toggle Speed (${speed})`}>
{tooltipProps => (
<div
{...tooltipProps}
role="button"
style={{
cursor: "pointer",
paddingTop: "4px",
paddingLeft: "4px",
paddingRight: "4px",
}}
onClick={e => {
console.log(e);
let newSpeed;
switch (speed) {
case 1:
newSpeed = 1.25;
break;
case 1.25:
newSpeed = 1.5;
break;
case 1.5:
newSpeed = 0.5;
break;
case 0.5:
newSpeed = 1;
break;
}
const parent = e.currentTarget.parentNode!.parentNode!.parentNode!;
// this works with audio too as it is but it doesnt always select the right audio tag
const media = parent.querySelector("video")! /* ?? document.querySelector("source")?.parentElement*/;
console.log(media);
media.playbackRate = newSpeed;
media.preservesPitch = settings.store.preservePitch;
setSpeed(newSpeed);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="19px" height="19px" viewBox="0 0 448 512"><path fill="currentColor" d={speedPaths[speed]}></path></svg>
</div>
)
}
</Tooltip >
</div >;
}, { noop: true })
});

View file

@ -100,7 +100,7 @@ async function loadAllBadges(noCache = false) {
let intervalId: any;
function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) {
export function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) {
return (
<Menu.Menu
navId="vc-badge-context"

View file

@ -57,7 +57,7 @@ export function VencordDonorModal() {
</ModalContent>
<ModalFooter>
<Flex justifyContent="center" style={{ width: "100%" }}>
<DonateButton link="https://github.com/sponsors/Vendicated" />
<DonateButton />
</Flex>
</ModalFooter>
</ModalRoot>
@ -113,7 +113,7 @@ export function EquicordDonorModal() {
</ModalContent>
<ModalFooter>
<Flex justifyContent="center" style={{ width: "100%" }}>
<DonateButton link="https://github.com/sponsors/thororen1234" />
<DonateButton equicord={true} />
</Flex>
</ModalFooter>
</ModalRoot>

View file

@ -0,0 +1,24 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "HeaderBarAPI",
description: "API to add buttons to the header bar.",
authors: [EquicordDevs.Prism],
patches: [
{
find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /(?<=\i\.Fragment,\{children:\[.{1,500}className:\i\}\))(?=\])/,
replace: ",...Vencord.Api.HeaderBar._addButtons()"
}
}
]
});

View file

@ -4,14 +4,16 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import alwaysExpandProfiles from "@equicordplugins/alwaysExpandProfiles";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { ContextMenuApi, Menu } from "@webpack/common";
import { ContextMenuApi, Menu, UserStore } from "@webpack/common";
interface UserProfileProps {
popoutProps: Record<string, any>;
@ -37,6 +39,10 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
disabled={getCurrentChannel()?.getGuildId() == null}
action={e => {
if (isPluginEnabled(alwaysExpandProfiles.name)) {
const user = UserStore.getCurrentUser();
return alwaysExpandProfiles.openUserModal(user);
}
openAlternatePopout = true;
accountPanelRef.current?.click();
}}

View file

@ -91,21 +91,24 @@ export default definePlugin({
replacement: [
// Use Decor avatar decoration hook
{
match: /(?<=\i\)\({avatarDecoration:)(\i)(?=,)(?<=currentUser:(\i).+?)/,
match: /(?<=\i\)\({avatarDecoration:)\i(?=,)(?<=currentUser:(\i).+?)/,
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
}
]
},
// Messages
{
find: '"Message Username"',
...[
'"Message Username"', // Messages
".nameplatePreview,{", // Nameplate preview
"#{intl::ayozFl::raw}", // Avatar preview
].map(find => ({
find,
replacement: [
{
match: /(?<=userValue.{0,25}void 0:)((\i)\.avatarDecoration)/,
replace: "$self.useUserDecorAvatarDecoration($2)??$1"
}
]
},
})),
],
settings,

View file

@ -10,5 +10,5 @@ import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
export const cl = classNameFactory("vc-decor-");
export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal");
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,300}Promise\.all/]);
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["initialSelectedDecoration:", /initialSelectedDecoration:\i,.{0,300}\i\.e\(/]);
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);

View file

@ -27,7 +27,7 @@ export default definePlugin({
{
find: "#{intl::UNIGNORE_TO_JUMP_BODY}",
replacement: {
match: /return \i\.\i\.isBlockedForMessage\(/,
match: /if\(\i\.\i\.isBlockedForMessage\(/,
replace: "return true;$&"
}
}

View file

@ -18,9 +18,6 @@
import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings, migratePluginSetting, Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
@ -192,49 +189,22 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor
);
};
function toggleMemberListDecorators(enabled: boolean) {
if (enabled) {
addMemberListDecorator("PlatformIndicators", props => <PlatformIndicator user={props.user} isMemberList />);
} else {
removeMemberListDecorator("PlatformIndicators");
}
}
function toggleNicknameIcons(enabled: boolean) {
if (enabled) {
addNicknameIcon("PlatformIndicators", props => <PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />, 1);
} else {
removeNicknameIcon("PlatformIndicators");
}
}
function toggleMessageDecorators(enabled: boolean) {
if (enabled) {
addMessageDecoration("PlatformIndicators", props => <PlatformIndicator user={props.message?.author} isMessage />);
} else {
removeMessageDecoration("PlatformIndicators");
}
}
migratePluginSetting("PlatformIndicators", "profiles", "badges");
const settings = definePluginSettings({
list: {
type: OptionType.BOOLEAN,
description: "Show indicators in the member list",
default: true,
onChange: toggleMemberListDecorators
},
profiles: {
type: OptionType.BOOLEAN,
description: "Show indicators in user profiles",
default: true,
onChange: toggleNicknameIcons
},
messages: {
type: OptionType.BOOLEAN,
description: "Show indicators inside messages",
default: true,
onChange: toggleMessageDecorators
},
colorMobileIndicator: {
type: OptionType.BOOLEAN,
@ -278,19 +248,21 @@ export default definePlugin({
name: "PlatformIndicators",
description: "Adds platform indicators (Desktop, Mobile, Web...) to users",
authors: [Devs.kemo, Devs.TheSun, Devs.Nuckyz, Devs.Ven],
dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
settings,
start() {
if (settings.store.list) toggleMemberListDecorators(true);
if (settings.store.profiles) toggleNicknameIcons(true);
if (settings.store.messages) toggleMessageDecorators(true);
renderNicknameIcon(props) {
if (!settings.store.profiles) return null;
return (
<PlatformIndicator user={UserStore.getUser(props.userId)} isProfile />
);
},
renderMemberListDecorator(props) {
if (!settings.store.list) return null;
return <PlatformIndicator user={props.user} isMemberList />;
stop() {
if (settings.store.list) toggleMemberListDecorators(false);
if (settings.store.profiles) toggleNicknameIcons;
if (settings.store.messages) toggleMessageDecorators(false);
},
renderMessageDecoration(props) {
if (!settings.store.messages) return null;
return <PlatformIndicator user={props.message?.author} isMessage />;
},
patches: [

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import "./style.css";
import { definePluginSettings, Settings } from "@api/Settings";
@ -11,15 +12,21 @@ import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { GuildMember, Message, User } from "@vencord/discord-types";
import { findByCodeLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore, MessageStore, RelationshipStore, StreamerModeStore, useEffect, UserStore } from "@webpack/common";
import { ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, RelationshipStore, StreamerModeStore, TextInput, useEffect, UserStore, useState } from "@webpack/common";
import { JSX } from "react";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { Button, ErrorBoundary, Heading, TextButton } from "@components/index";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index";
const wrapEmojis = findByCodeLazy("lastIndex;return");
const AccessibilityStore = findStoreLazy("AccessibilityStore");
const colorPattern = /^#(?:[\da-f]{3}){1,2}$|^#(?:[\da-f]{4}){1,2}$|(rgb|hsl)a?\((\s*-?\d+%?\s*,){2}(\s*-?\d+%?\s*)\)|(rgb|hsl)a?\((\s*-?\d+%?\s*,){3}\s*(0|(0?\.\d+)|1)\)$/iu;
const roleColorPattern = /^role((?:\+|-)\d{0,4})?$/iu;
const symbolPattern = /^[\p{S}\p{P}]{1,3}$/iu;
const templatePattern = /(?:\{(?:friend|nick|display|user)(?:,\s*(?:friend|nick|display|user))*\})/iu;
const templatePattern = /(?:\{(?:custom|friend|nick|display|user)(?:,\s*(?:custom|friend|nick|display|user))*\})/iu;
type CustomNicknameData = Record<string, string>;
let customNicknames: CustomNicknameData = {};
function adjustHex(color: string, percent: number): string {
let hex = color.replace("#", "");
@ -208,8 +215,8 @@ function validTemplate(value: string) {
suffix.length > 3 ||
(!!prefix && !symbolPattern.test(prefix)) ||
(!!suffix && !symbolPattern.test(suffix)) ||
targetProcessedNames.length > 4 ||
targetProcessedNames.some(name => !["friend", "nick", "display", "user"].includes(name.trim()))
targetProcessedNames.length > 5 ||
targetProcessedNames.some(name => !["custom", "friend", "nick", "display", "user"].includes(name.trim()))
);
});
@ -218,7 +225,13 @@ function validTemplate(value: string) {
}
}
function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): [string | null, string | null, string | null, string | null] {
function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolean, discriminators: boolean): {
username: string | null;
display: string | null;
nick: string | null;
friend: string | null;
custom: string | null;
} {
let discriminator: string | null = null;
if (discriminators) {
@ -254,7 +267,13 @@ function getProcessedNames(author: any, truncateAllNamesWithStreamerMode: boolea
? friendName[0] + "..."
: friendName as string;
return [username, display, nick, friend];
const customName: string | null = !author?.id ? null : customNicknames[author.id] || null;
const custom: string | null = !customName ? null
: StreamerModeStore.enabled && (truncateAllNamesWithStreamerMode || customName.toLowerCase() === author.username.toLowerCase())
? customName[0] + "..."
: customName;
return { username, display, nick, friend, custom };
}
interface mentionProps {
@ -351,8 +370,8 @@ function renderUsername(
const isReaction = isReactionsTooltip || isReactionsPopout;
const isVoice = type === "voiceChannel";
const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]);
const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config;
const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "memberList", "profilePopout", "reactions", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreGradients", "ignoreFonts", "animateGradients", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]);
const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, ignoreFonts, animateGradients, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config;
const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : false);
const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d";
@ -361,14 +380,16 @@ function renderUsername(
const resolvedDisplayNameColor = author ? resolveColor(author, displayNameColor.trim(), "", canUseGradient) : null;
const resolvedNicknameColor = author ? resolveColor(author, nicknameColor.trim(), "", canUseGradient) : null;
const resolvedFriendNameColor = author ? resolveColor(author, friendNameColor.trim(), "", canUseGradient) : null;
const resolvedCustomNameColor = author ? resolveColor(author, customNameColor.trim(), "", canUseGradient) : null;
const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" };
const [username, display, nick, friend] = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators);
const { username, display, nick, friend, custom } = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators);
const names: Record<string, [string | null, object | null]> = {
user: [username, resolvedUsernameColor],
display: [display, resolvedDisplayNameColor],
nick: [nick, resolvedNicknameColor],
friend: [friend, resolvedFriendNameColor],
custom: [custom, resolvedCustomNameColor]
};
const outputs: any[] = [];
@ -421,6 +442,7 @@ function renderUsername(
let second = outputs.shift();
let third = outputs.shift();
let fourth = outputs.shift();
let fifth = outputs.shift();
const firstValueWrapped = hookless ?
(first.name || "")
@ -438,10 +460,15 @@ function renderUsername(
((fourth ?? {}).name || "")
: wrapEmojis((fourth ?? {}).name || "");
const fifthValueWrapped = hookless ?
((fifth ?? {}).name || "")
: wrapEmojis((fifth ?? {}).name || "");
first.wrapped = firstValueWrapped;
second && (second.wrapped = secondValueWrapped);
third && (third.wrapped = thirdValueWrapped);
fourth && (fourth.wrapped = fourthValueWrapped);
fifth && (fifth.wrapped = fifthValueWrapped);
if (isMessage && !messages) {
return [null, null, null];
@ -462,10 +489,14 @@ function renderUsername(
return [fallbackText, <>{fallbackText}</>, fallbackText];
}
const prioritizeUsername = (new Set([first, second, third, fourth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1);
const prioritizeUsername = (new Set([first, second, third, fourth, fifth].filter(Boolean).map(pos => pos.name.toLowerCase())).size > 1);
if (removeDuplicates) {
// Remove duplicates from back to front. Prioritize the earlier name, unless it's the username and there's more than one unique option, then prioritize it.
fifth && fourth && fifth.name.toLowerCase() === fourth.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? fourth = null : fifth = null : null;
fifth && third && fifth.name.toLowerCase() === third.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? third = null : fifth = null : null;
fifth && second && fifth.name.toLowerCase() === second.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? second = null : fifth = null : null;
fifth && first && fifth.name.toLowerCase() === first.name.toLowerCase() ? fifth.type === "user" && prioritizeUsername ? first = null : fifth = null : null;
fourth && third && fourth.name.toLowerCase() === third.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? third = null : fourth = null : null;
fourth && second && fourth.name.toLowerCase() === second.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? second = null : fourth = null : null;
fourth && first && fourth.name.toLowerCase() === first.name.toLowerCase() ? fourth.type === "user" && prioritizeUsername ? first = null : fourth = null : null;
@ -474,11 +505,12 @@ function renderUsername(
second && first && second.name.toLowerCase() === first.name.toLowerCase() ? second.type === "user" && prioritizeUsername ? first = null : second = null : null;
}
const remainingNames = [first, second, third, fourth].filter(Boolean);
const remainingNames = [first, second, third, fourth, fifth].filter(Boolean);
first = remainingNames.shift();
second = remainingNames.shift();
third = remainingNames.shift();
fourth = remainingNames.shift();
fifth = remainingNames.shift();
const useTopRoleStyle = isMention || isReactionsPopout;
const topRoleStyle = resolveColor(author, "Role", "", canUseGradient);
@ -500,7 +532,8 @@ function renderUsername(
const secondDataText = second && shouldAnimateSecondaryNames ? (second.prefix + second.name + second.suffix) : "";
const thirdDataText = third && shouldAnimateSecondaryNames ? (third.prefix + third.name + third.suffix) : "";
const fourthDataText = fourth && shouldAnimateSecondaryNames ? (fourth.prefix + fourth.name + fourth.suffix) : "";
const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText].filter(Boolean).join(nameSeparator).trim();
const fifthDataText = fifth && shouldAnimateSecondaryNames ? (fifth.prefix + fifth.name + fifth.suffix) : "";
const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText, fifthDataText].filter(Boolean).join(nameSeparator).trim();
// Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages.
const hoveringClass = (isHovering ? " show-me-your-name-gradient-hovered" : "");
@ -512,10 +545,12 @@ function renderUsername(
const secondGroupClasses = "show-me-your-name-name-group show-me-your-name-second-name-group";
const thirdGroupClasses = "show-me-your-name-name-group show-me-your-name-third-name-group";
const fourthGroupClasses = "show-me-your-name-name-group show-me-your-name-fourth-name-group";
const fifthGroupClasses = "show-me-your-name-name-group show-me-your-name-fifth-name-group";
const firstNameClasses = "show-me-your-name-name show-me-your-name-first-name";
const secondNameClasses = "show-me-your-name-name show-me-your-name-second-name";
const thirdNameClasses = "show-me-your-name-name show-me-your-name-third-name";
const fourthNameClasses = "show-me-your-name-name show-me-your-name-fourth-name";
const fifthNameClasses = "show-me-your-name-name show-me-your-name-fifth-name";
const prefixClasses = "show-me-your-name-affix show-me-your-name-prefix";
const suffixClasses = "show-me-your-name-affix show-me-your-name-suffix";
@ -549,85 +584,39 @@ function renderUsername(
{first.wrapped}</span>
</span>
)}
{second && (
{[
{ name: second, dataText: secondDataText, groupClass: secondGroupClasses, nameClass: secondNameClasses, position: "second" },
{ name: third, dataText: thirdDataText, groupClass: thirdGroupClasses, nameClass: thirdNameClasses, position: "third" },
{ name: fourth, dataText: fourthDataText, groupClass: fourthGroupClasses, nameClass: fourthNameClasses, position: "fourth" },
{ name: fifth, dataText: fifthDataText, groupClass: fifthGroupClasses, nameClass: fifthNameClasses, position: "fifth" }
].map(({ name, dataText, groupClass, nameClass, position }) => name && (
<span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + secondGroupClasses) : secondGroupClasses)}
data-text={shouldGradientGlow && secondDataText ? secondDataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? second.style.gradient.animated : undefined) as React.CSSProperties}
key={position}
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + groupClass) : groupClass)}
data-text={shouldGradientGlow && dataText ? dataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? name.style.gradient.animated : undefined) as React.CSSProperties}
>
<span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span>
{second.prefix}</span>
{name.prefix}</span>
<span
// On non-primary names, allow disabling the gradients completely, or just their animation & glow.
className={secondNameClasses}
className={nameClass}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients
? second.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && second.style.gradient
? second.style.gradient.animated
: second.style.gradient
? second.style.gradient.static.original
: second.style.normal.adjusted)
? name.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && name.style.gradient
? name.style.gradient.animated
: name.style.gradient
? name.style.gradient.static.original
: name.style.normal.adjusted)
}}>
{second.wrapped}</span>
{name.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{second.suffix}</span>
{name.suffix}</span>
</span>
)}
{third && (
<span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + thirdGroupClasses) : thirdGroupClasses)}
data-text={shouldGradientGlow && thirdDataText ? thirdDataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? third.style.gradient.animated : undefined) as React.CSSProperties}
>
<span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span>
{third.prefix}</span>
<span
className={thirdNameClasses}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients
? third.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && third.style.gradient
? third.style.gradient.animated
: third.style.gradient
? third.style.gradient.static.original
: third.style.normal.adjusted)
}}>
{third.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{third.suffix}</span>
</span>
)}
{fourth && (
<span
className={(shouldGradientGlow && shouldAnimateSecondaryNames ? (gradientClasses + " " + fourthGroupClasses) : fourthGroupClasses)}
data-text={shouldGradientGlow && fourthDataText ? fourthDataText : undefined}
style={(shouldGradientGlow && shouldAnimateSecondaryNames ? fourth.style.gradient.animated : undefined) as React.CSSProperties}
>
<span style={affixColor as React.CSSProperties} className={prefixClasses}>
<span>{nameSeparator}</span>
{fourth.prefix}</span>
<span
className={fourthNameClasses}
style={{
...(ignoreFonts ? { "font-family": "var(--font-primary)", "letter-spacing": "normal" } : {}),
...(ignoreGradients
? fourth.style.normal.adjusted
: shouldAnimateGradients && shouldAnimateSecondaryNames && fourth.style.gradient
? fourth.style.gradient.animated
: fourth.style.gradient
? fourth.style.gradient.static.original
: fourth.style.normal.adjusted)
}}>
{fourth.wrapped}</span>
<span style={affixColor as React.CSSProperties} className={suffixClasses}>
{fourth.suffix}</span>
</span>
)}
))}
</span>
);
@ -688,6 +677,98 @@ function removeHoveringReactionPopout(id: string) {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}
function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
const [value, setValue] = useState(customNicknames[user.id] ?? "");
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Heading tag="h1" style={{ flexGrow: 1, margin: 0 }}>
{customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "16px", fontWeight: "400", lineHeight: "1.25", color: "var(--text-secondary)" }}>
{"Set a custom SMYN nickname for this user. Make use of it by specifying {custom} in the SMYN template settings."}
</Heading>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
SMYN Nickname
</Heading>
<TextInput
value={value}
maxLength={32}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
<TextButton
className="show-me-your-name-reset-button"
onClick={async () => {
setValue("");
delete customNicknames[user.id];
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}}
>
Reset SMYN Nickname
</TextButton>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
</ModalContent>
<ModalFooter className="show-me-your-name-modal-footer-container">
<Button
variant="primary"
onClick={async () => {
const trimmed = value.trim().slice(0, 32).trim();
if (trimmed) {
customNicknames[user.id] = trimmed;
} else {
delete customNicknames[user.id];
}
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
modalProps.onClose();
}}
>
Save
</Button>
<Button
variant="secondary"
style={{ marginRight: "8px" }}
onClick={modalProps.onClose}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
);
}
const userContextPatch: NavContextMenuPatchCallback = (children, { user }) => {
if (!user) return;
const currentUser = UserStore.getCurrentUser();
if (!currentUser || user.id === currentUser.id) return;
const group = findGroupChildrenByChildId("user-profile", children);
!group && children.push(<Menu.MenuSeparator />);
(group || children).push(
<Menu.MenuItem
id="smyn-custom-nickname"
label={customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
action={() => openModal(props => (
<ErrorBoundary>
<CustomNicknameModal modalProps={props} user={user} />
</ErrorBoundary>
))}
/>
);
};
const settings = definePluginSettings({
messages: {
type: OptionType.BOOLEAN,
@ -747,17 +828,17 @@ const settings = definePluginSettings({
ignoreFonts: {
type: OptionType.BOOLEAN,
default: false,
description: "For the second, third, and fourth names, use GG SANS regardless of the user's custom font.",
description: "For the non-primary names, use Discord's default fonts regardless of the user's custom nitro font.",
},
ignoreGradients: {
type: OptionType.BOOLEAN,
default: true,
description: "For the second, third, and fourth names, if the role has a gradient, ignore it in favor of the value below.",
description: "For the non-primary names, if the role has a gradient, ignore it in favor of the color set below.",
},
animateGradients: {
type: OptionType.BOOLEAN,
default: false,
description: "For the second, third, and fourth names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.",
description: "For the non-primary names, if the role has a gradient, animate it. This is disabled by \"Ignore Gradients\" and reduced motion.",
},
nameSeparator: {
type: OptionType.STRING,
@ -766,10 +847,16 @@ const settings = definePluginSettings({
},
includedNames: {
type: OptionType.STRING,
description: "The order to display usernames, display names, nicknames, and friend names. Use the following placeholders: {user}, {display}, {nick}, {friend}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {friend, nick, display}. You can have up to three prefixes and three suffixes per name.",
default: "{friend, nick} [{display}] (@{user})",
description: "The order to display usernames, display names, nicknames, friend names, and custom names. Use the following placeholders: {user}, {display}, {nick}, {friend}, {custom}. You can provide multiple name options to use as fallbacks if one is unavailable by separating them with commas as such: {custom, friend, nick}. You can have up to three prefixes and three suffixes per name.",
default: "{custom, friend, nick} [{display}] (@{user})",
isValid: validTemplate,
},
customNameColor: {
type: OptionType.STRING,
description: "The color to use for the custom name you assigned a user if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")",
default: "Role-25",
isValid: validColor,
},
friendNameColor: {
type: OptionType.STRING,
description: "The color to use for a friend's nickname if it's not the first displayed. Leave blank for default. Accepts hex(a), rgb(a), or hsl(a) input. Use \"Role\" to follow the user's top role color. Use \"Role+-#\" to adjust the brightness by that percentage (ex: \"Role+15\")",
@ -804,8 +891,9 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ShowMeYourName",
description: "Display any permutation of nicknames, display names, and usernames in chat.",
authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan],
description: "Display any permutation of custom nicknames, friend nicknames, server nicknames, display names, and usernames in chat.",
authors: [Devs.Rini, Devs.TheKodeToad, EquicordDevs.Etorix, Devs.sadan, EquicordDevs.Prism],
tags: ["SMYN", "Nicknames", "Custom Nicknames",],
isModified: true,
settings,
@ -943,6 +1031,15 @@ export default definePlugin({
}
],
async start() {
const data = await DataStore.get<CustomNicknameData>("SMYNCustomNicknames");
customNicknames = data ?? {};
},
contextMenus: {
"user-context": userContextPatch
},
flux: {
RELATIONSHIP_UPDATE(data) {
// Allows rerendering when changing friend names.

View file

@ -37,4 +37,14 @@
.show-me-your-name-gradient-hovered::after {
opacity: .7;
transition: opacity .1s ease-in-out
}
.show-me-your-name-modal-footer-container button {
width: 100%;
}
.show-me-your-name-reset-button {
margin-top: 8px;
font-size: 14px;
font-weight: 500;
}

View file

@ -201,6 +201,14 @@ const SilentTypingChatToggle: ChatBarButtonFactory = ({ channel, type }) => {
);
};
function SilentTypingChatIcon() {
return (
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style={{ scale: "1.2" }}>
<path fill="currentColor" d="M18.333 15.556H1.667a1.667 1.667 0 0 1 -1.667 -1.667v-10a1.667 1.667 0 0 1 1.667 -1.667h16.667a1.667 1.667 0 0 1 1.667 1.667v10a1.667 1.667 0 0 1 -1.667 1.667M4.444 6.25V4.861a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V4.861a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V6.25a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333V8.194a0.417 0.417 0 0 0 -0.417 -0.417H4.306a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417H7.639a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m3.333 0V8.194a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V9.583a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m-11.667 3.333v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H2.639a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417m10 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417H5.973a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h8.056a0.417 0.417 0 0 0 0.417 -0.417m3.333 0v-1.389a0.417 0.417 0 0 0 -0.417 -0.417h-1.389a0.417 0.417 0 0 0 -0.417 0.417V12.917a0.417 0.417 0 0 0 0.417 0.417h1.389a0.417 0.417 0 0 0 0.417 -0.417" transform="translate(2, 3)" />
</svg>
);
}
function getEffectiveList(): string[] {
if (settings.store.defaultHidden) {
if (!settings.store.disabledLocations) {
@ -288,7 +296,6 @@ export default definePlugin({
name: "SilentTyping",
authors: [Devs.Ven, Devs.Rini, Devs.ImBanana, EquicordDevs.Etorix],
description: "Hide your typing indicator from chat.",
dependencies: ["ChatInputButtonAPI"],
isModified: true,
settings,
@ -298,6 +305,10 @@ export default definePlugin({
contextMenus: {
"textarea-context": ChatBarContextCheckbox
},
chatBarButton: {
icon: SilentTypingChatIcon,
render: SilentTypingChatToggle
},
patches: [
{
@ -425,6 +436,4 @@ export default definePlugin({
if (checkEnabled(channelId)) return;
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
},
renderChatBarButton: SilentTypingChatToggle,
});

View file

@ -18,9 +18,6 @@
import "./style.css";
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -52,9 +49,22 @@ export default definePlugin({
name: "UserVoiceShow",
description: "Shows an indicator when a user is in a Voice Channel",
authors: [Devs.Nuckyz, Devs.LordElias, EquicordDevs.omaw],
dependencies: ["NicknameIconsAPI", "MemberListDecoratorsAPI", "MessageDecorationsAPI"],
settings,
renderNicknameIcon({ userId }) {
if (!settings.store.showInUserProfileModal) return null;
return (
<VoiceChannelIndicator userId={userId} isProfile />
);
},
renderMemberListDecorator({ user }) {
if (!settings.store.showInMemberList) return null;
return user == null ? null : <VoiceChannelIndicator userId={user.id} />;
},
renderMessageDecoration({ message }) {
if (!settings.store.showInMessages) return null;
return message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />;
},
patches: [
// Friends List
{
@ -67,23 +77,5 @@ export default definePlugin({
}
],
start() {
if (settings.store.showInUserProfileModal) {
addNicknameIcon("UserVoiceShow", ({ userId }) => <VoiceChannelIndicator userId={userId} isProfile />);
}
if (settings.store.showInMemberList) {
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
}
if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
}
},
stop() {
removeNicknameIcon("UserVoiceShow");
removeMemberListDecorator("UserVoiceShow");
removeMessageDecoration("UserVoiceShow");
},
VoiceChannelIndicator
});

View file

@ -632,7 +632,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
alfred: {
name: "alfred",
id: 1038466644353232967n
}
},
} satisfies Record<string, Dev>);
export const EquicordDevs = Object.freeze({
@ -1205,7 +1205,7 @@ export const EquicordDevs = Object.freeze({
name: "neoarz",
id: 1015372540937502851n
},
KamiRu:{
KamiRu: {
name: "KamiRu",
id: 819191621676695563n
},

View file

@ -24,9 +24,11 @@ import { Patch, PatchReplacement, ReplaceFn } from "./types";
// @ts-expect-error "RegExp.escape" is very new and not yet in DOM types
const escapeRegex = RegExp.escape || ((s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
export function getReplacement(isString: boolean, hashed: string) {
export function getReplacement(isString: boolean, hashed: string, hash: boolean = false) {
const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/");
if (hash) return hashed;
if (hasSpecialChars) {
return isString
? `["${hashed}"]`
@ -48,7 +50,7 @@ function canonicalizeMatchCompatString(str: string) {
const re = /#{intl::([\w$+/]*)(?:::(\w+))?}/g;
for (const match of str.matchAll(re)) {
result += escapeRegex(str.slice(lastIndex, match.index));
result += getCompatReplacement(match[1]);
result += match[2] === "raw" ? getReplacement(false, match[1]) : match[2] === "hash" ? getReplacement(false, runtimeHashMessageKey(match[1]), true) : getCompatReplacement(match[1]);
lastIndex = (match.index ?? 0) + match[0].length;
}
result += escapeRegex(str.slice(lastIndex));
@ -63,6 +65,7 @@ export function canonicalizeMatch<T extends RegExp | string>(match: T): T extend
let partialCanon = typeof match === "string" ? match : match.source;
partialCanon = partialCanon.replaceAll(/#{intl::([\w$+/]*)(?:::(\w+))?}/g, (_, key, modifier) => {
if (modifier === "raw") return getReplacement(false, key);
if (modifier === "hash") return getReplacement(false, runtimeHashMessageKey(key), true);
return getCompatReplacement(key);
});

View file

@ -16,9 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { AudioProcessor } from "@api/AudioPlayer";
import type { ProfileBadge } from "@api/Badges";
import type { ChatBarButtonData, ChatBarButtonFactory } from "@api/ChatButtons";
import type { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { HeaderBarButtonFactory } from "@api/HeaderBar";
import type { MemberListDecoratorFactory } from "@api/MemberListDecorators";
import type { MessageAccessoryFactory } from "@api/MessageAccessories";
import type { MessageDecorationFactory } from "@api/MessageDecorations";
@ -195,7 +197,11 @@ export interface PluginDef {
renderMessageDecoration?: MessageDecorationFactory;
renderMemberListDecorator?: MemberListDecoratorFactory;
// Custom
renderNicknameIcon?: NicknameIconFactory;
renderHeaderBarButton?: HeaderBarButtonFactory;
audioProcessor?: AudioProcessor;
// TODO: Remove eventually
/**

View file

@ -18,6 +18,7 @@
import type * as t from "@vencord/discord-types";
import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack";
import type * as TSPattern from "ts-pattern";
export let FluxDispatcher: t.FluxDispatcher;
waitFor(["dispatch", "subscribe"], m => {
@ -51,7 +52,7 @@ export const useDrag = findByCodeLazy("useDrag::spec.begin was deprecated");
// you cant make a better finder i love that they remove display names sm
export const useDrop = findByCodeLazy(".options);return", ".collect,");
export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", {
export const { match, P }: Pick<typeof TSPattern, "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", {
match: filters.byCode("return new"),
P: filters.byProps("when")
});