This commit is contained in:
thororen1234 2024-06-21 20:05:37 -04:00
parent 65bb12c33b
commit 76746c0c4a
17 changed files with 74 additions and 548 deletions

View file

@ -21,7 +21,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- Request for plugins from Discord.
<details>
<summary>Extra included plugins (61 additional plugins)</summary>
<summary>Extra included plugins (59 additional plugins)</summary>
- AllCallTimers by MaxHerbold and D3SOX
- AltKrispSwitch by newwares
@ -32,7 +32,6 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- BlockKrsip by D3SOX
- BypassDND by Inbestigator
- CleanChannelName by AutumnVN
- ColorMessage by Kyuuhachi
- CopyUserMention by Cortex and castdrian
- CustomAppIcons by Happy Enderman and SerStars
- DNDWhilePlaying by thororen
@ -70,7 +69,6 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
- Search by JacobTm and thororen
- SearchFix by Jaxx
- Sekai Stickers by MaiKokain
- ServerProfilesToolbox by D3SOX
- ShowBadgesInChat by Inbestigator and KrystalSkull
- Slap by Korbo
- SoundBoardLogger by Moxxie, fres, echo, thororen

View file

@ -15,7 +15,7 @@ export default definePlugin({
{
find: ",setNoiseCancellation(",
replacement: {
match: /(}\),)(.{1,2}\.default\.dispatch\({type:"AUDIO_SET_NOISE_SUPPRESSION",)/,
match: /(}\),)(.{1,2}\.\i\.dispatch\({type:"AUDIO_SET_NOISE_SUPPRESSION",)/,
replace: "$1!$self.shouldCancelSuppression(arguments)&&$2"
}
}

View file

@ -57,14 +57,14 @@ export default definePlugin({
predicate: () => settings.store.dms,
},
{ // Above DMs, keyboard nav
find: ".default.hasLibraryApplication()&&!",
find: ".hasLibraryApplication()&&!",
replacement: [
{
match: /\i\.Routes\.APPLICATION_STORE,/,
match: /\i\.\i\.APPLICATION_STORE,/,
replace: "/*$&*/",
},
{
match: /\i\.Routes\.COLLECTIBLES_SHOP,/,
match: /\i\.\i\.COLLECTIBLES_SHOP,/,
replace: "/*$&*/",
},
],
@ -87,9 +87,9 @@ export default definePlugin({
predicate: () => settings.store.gift,
},
{ // Emoji list
find: "useEmojiGrid:function()",
find: /\i\.\i\i\.getEmojiUnavailableReason/,
replacement: {
match: /(\w+)=!\w+&&\w+.default.isEmojiCategoryNitroLocked\(\{[^}]*\}\);/,
match: /(\w+)=!\w+&&\w+.\i.isEmojiCategoryNitroLocked\(\{[^}]*\}\);/,
replace: "$&$1||"
},
predicate: () => settings.store.emojiList,

View file

@ -255,7 +255,7 @@ export default definePlugin({
},
{
// Show all activities in the profile panel
find: /.UserProfileTypes.PANEL,themeOverride:\i\i/,
find: /\i\.\i\i\.PANEL,themeOverride:\i\i,/,
replacement: {
match: /(?<=\(0,\i\.jsx\)\()\i\.\i(?=,{activity:.+?,user:\i,channelId:\i.id,)/,
replace: "$self.showAllActivitiesComponent"

View file

@ -9,8 +9,8 @@ import "./style.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { extractAndLoadChunksLazy, findByPropsLazy, findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { useEffect, useState } from "@webpack/common";
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { NavigationRouter, useEffect, useState } from "@webpack/common";
const LinkButton = findExportedComponentLazy("LinkButton"); // let {route: e, selected: t, icon: n, iconClassName: a, interactiveClassName: s, text: r, children: o, locationState: d, onClick: f, className: p, role: m, "aria-posinset": C, "aria-setsize": g, ...E} = this.props;
@ -20,7 +20,6 @@ const QuestsComponent = findComponentByCodeLazy(".questsContainer"); // No nesse
const questsStore = findStoreLazy("QuestsStore");
const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\).{0,50}"UserSettings"/);
const nav: NavigationSettings = findByPropsLazy("transitionTo", "transitionToGuild", "getHistory");
// Routes used in this plugin (in case someone wants to add new ones)
@ -82,14 +81,10 @@ const QuestButtonComponent = () => {
const redirectRoute = (ev: BeforeUnloadEvent) => {
const paths = Array.from(routes.keys());
const path = nav.getHistory().location.pathname;
if (paths.includes(path)) {
const data = routes.get(path);
ev.preventDefault();
nav.transitionTo(data?.redirectTo ?? "/channels/@me");
setTimeout(() => window.location.reload(), 0);
}
ev.preventDefault();
NavigationRouter.transitionTo("/quests/@me");
setTimeout(() => window.location.reload(), 0);
};
export default definePlugin({
@ -110,23 +105,23 @@ export default definePlugin({
patches: [
{ // Add new quest button
find: "\"discord-shop\"",
find: "\"discord-shop\"),",
replacement: {
match: /"discord-shop"\),/,
replace: "$&,$self.QuestButtonComponent(),"
}
},
{ // Add new route
find: "Routes.MESSAGE_REQUESTS,render:",
find: ".MESSAGE_REQUESTS,render:",
replacement: {
match: /\((0,.{0,10}\.jsx\)\(.{0,10}\.default,){path:.{0,10}\.Routes\.MESSAGE_REQUESTS,.{0,100}?\),/,
match: /\((0,.{0,10}\.jsx\)\(.{0,10}\.\i,){path:.{0,10}\.\i\.MESSAGE_REQUESTS,.{0,100}?\),/,
replace: "$&...$self.routes.map(r => (($1r))),"
}
},
{
find: 'on("LAUNCH_APPLICATION"',
replacement: {
match: /path:\[.{0,500}Routes\.MESSAGE_REQUESTS,/,
match: /path:\[.{0,500}\i\.MESSAGE_REQUESTS,/,
replace: "$&...$self.paths,"
}
}

View file

@ -1,62 +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 * as Styles from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const AuthorStore = findByPropsLazy("useNullableMessageAuthor", "useNullableMessageAuthor");
import style from "./style.css?managed";
export const settings = definePluginSettings({
saturation: {
type: OptionType.SLIDER,
description: "Message color saturation",
markers: makeRange(0, 100, 10),
default: 20,
onChange() {
updateStyle();
},
},
});
function updateStyle() {
(Styles.requireStyle(style).dom!.sheet!.cssRules[0] as CSSStyleRule)
.style.setProperty("--98-message-color-saturation", `${settings.store.saturation}`);
}
export default definePlugin({
name: "ColorMessage",
description: "Colors message content with author's role color",
authors: [Devs.Kyuuhachi],
settings,
patches: [
{
find: 'default.Messages.MESSAGE_EDITED,")"',
replacement: {
match: /id:\(0,\w+.getMessageContentId\)\((\w+)\),/,
replace: '$&style:{"--98-message-color":$self.getMessageColor($1)},'
}
},
],
getMessageColor(messageId: string) {
return AuthorStore.default(messageId).colorString;
},
start() {
Styles.enableStyle(style);
updateStyle();
},
stop() {
Styles.disableStyle(style);
},
});

View file

@ -1,12 +0,0 @@
/* stylelint-disable custom-property-pattern */
:root {
--98-message-color-saturation: /*DYNAMIC*/;
}
div[class*="messageContent_"] {
color: color-mix(
in lab,
var(--98-message-color, var(--text-normal)) calc(var(--98-message-color-saturation) * 1%),
var(--text-normal)
)
}

View file

@ -62,8 +62,8 @@ export default definePlugin({
{
find: "fetchRelationships(){",
replacement: {
match: /\.then\(e=>o\.default\.dispatch\({type:"LOAD_RELATIONSHIPS_SUCCESS",relationships:e\.body}\)/,
replace: ".then(e=>{o.default.dispatch({type:\"LOAD_RELATIONSHIPS_SUCCESS\",relationships:e.body}); $self.getContacts(e.body)}"
match: /\.then\(\i=>\i\.\i\.dispatch\({type:"LOAD_RELATIONSHIPS_SUCCESS",relationships:(\i\.body)}\);/,
replace: "$&$self.getContacts($2)}"
}
},
{

View file

@ -8,6 +8,7 @@ import "./style.css";
import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { EquicordDevs } from "@utils/constants";
@ -15,31 +16,32 @@ import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, SearchableSelect, SelectedChannelStore, TabBar, TextInput, UserStore, UserUtils, useState } from "@webpack/common";
import { Button, ChannelStore, Forms, SearchableSelect, SelectedChannelStore, TabBar, TextInput, UserStore, useState } from "@webpack/common";
import { Message, User } from "discord-types/general/index.js";
let keywordEntries: Array<{ regex: string, listIds: Array<string>, listType: ListType; }> = [];
let currentUser: User;
let keywordLog: Array<any> = [];
const MenuHeader = findByCodeLazy(".useInDesktopNotificationCenterExperiment)()?");
const Popout = findByCodeLazy("let{analyticsName:");
const MenuHeader = findByCodeLazy(".sv)()?(0,");
const Popout = findByCodeLazy(".loadingMore&&null==");
const recentMentionsPopoutClass = findByPropsLazy("recentMentionsPopout");
const createMessageRecord = findByCodeLazy("THREAD_CREATED?[]:(0,");
const KEYWORD_ENTRIES_KEY = "KeywordNotify_keywordEntries";
const KEYWORD_LOG_KEY = "KeywordNotify_log";
const { createMessageRecord } = findByPropsLazy("createMessageRecord", "updateMessageRecord");
const cl = classNameFactory("vc-keywordnotify-");
async function addKeywordEntry(updater: () => void) {
async function addKeywordEntry(forceUpdate: () => void) {
keywordEntries.push({ regex: "", listIds: [], listType: ListType.BlackList });
await DataStore.set(KEYWORD_ENTRIES_KEY, keywordEntries);
updater();
forceUpdate();
}
async function removeKeywordEntry(idx: number, updater: () => void) {
async function removeKeywordEntry(idx: number, forceUpdate: () => void) {
keywordEntries.splice(idx, 1);
await DataStore.set(KEYWORD_ENTRIES_KEY, keywordEntries);
updater();
forceUpdate();
}
function safeMatchesRegex(s: string, r: string) {
@ -50,7 +52,6 @@ function safeMatchesRegex(s: string, r: string) {
}
}
enum ListType {
BlackList = "BlackList",
Whitelist = "Whitelist"
@ -90,7 +91,7 @@ function Collapsible({ title, children }) {
onClick={() => setIsOpen(!isOpen)}
look={Button.Looks.BLANK}
size={Button.Sizes.ICON}
className="keywordnotify-collapsible">
className={cl("collapsible")}>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ marginLeft: "auto", color: "var(--text-muted)", paddingRight: "5px" }}>{isOpen ? "▼" : "▶"}</div>
<Forms.FormTitle tag="h4">{title}</Forms.FormTitle>
@ -130,7 +131,7 @@ function ListedIds({ listIds, setListIds }) {
}}
look={Button.Looks.BLANK}
size={Button.Sizes.ICON}
className="keywordnotify-delete">
className={cl("delete")}>
<DeleteIcon />
</Button>
</Flex>
@ -200,18 +201,18 @@ function KeywordEntries() {
onClick={() => removeKeywordEntry(i, update)}
look={Button.Looks.BLANK}
size={Button.Sizes.ICON}
className="keywordnotify-delete">
className={cl("delete")}>
<DeleteIcon />
</Button>
</Flex>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormDivider className={[Margins.top8, Margins.bottom8].join(" ")} />
<Forms.FormTitle tag="h5">Whitelist/Blacklist</Forms.FormTitle>
<Flex flexDirection="row">
<div style={{ flexGrow: 1 }}>
<ListedIds listIds={values[i].listIds} setListIds={e => setListIds(i, e)} />
</div>
</Flex>
<div className={Margins.top8 + " " + Margins.bottom8} />
<div className={[Margins.top8, Margins.bottom8].join(" ")} />
<Flex flexDirection="row">
<Button onClick={() => {
values[i].listIds.push("");
@ -254,7 +255,7 @@ export default definePlugin({
settings,
patches: [
{
find: "}_dispatch(",
find: "Dispatch.dispatch(...) called without an action type",
replacement: {
match: /}_dispatch\((\i),\i\){/,
replace: "$&$1=$self.modify($1);"
@ -268,9 +269,9 @@ export default definePlugin({
}
},
{
find: "InboxTab.TODOS?(",
find: "location:\"RecentsPopout\"",
replacement: {
match: /:\i&&(\i)===\i\.InboxTab\.TODOS.{1,50}setTab:(\i),onJump:(\i),closePopout:(\i)/,
match: /:(\i)===\i\.\i\.MENTIONS\?\(0,.+?setTab:(\i),onJump:(\i),badgeState:\i,closePopout:(\i)/,
replace: ": $1 === 5 ? $self.tryKeywordMenu($2, $3, $4) $&"
}
},
@ -280,12 +281,19 @@ export default definePlugin({
match: /function (\i)\(\i\){let{message:\i,gotoMessage/,
replace: "$self.renderMsg = $1; $&"
}
},
{
find: ".guildFilter:null",
replacement: {
match: /onClick:\(\)=>(\i\.\i\.deleteRecentMention\((\i)\.id\))/,
replace: "onClick: () => $2._keyword ? $self.deleteKeyword($2.id) : $1"
}
}
],
async start() {
keywordEntries = await DataStore.get(KEYWORD_ENTRIES_KEY) ?? [];
currentUser = await UserUtils.getUser(UserStore.getCurrentUser().id);
currentUser = UserStore.getCurrentUser();
this.onUpdate = () => null;
(await DataStore.get(KEYWORD_LOG_KEY) ?? []).map(e => JSON.parse(e)).forEach(e => {
@ -353,6 +361,10 @@ export default definePlugin({
if (m == null || keywordLog.some(e => e.id === m.id))
return;
DataStore.get(KEYWORD_LOG_KEY).then(log => {
DataStore.set(KEYWORD_LOG_KEY, [...log, JSON.stringify(m)]);
});
const thing = createMessageRecord(m);
keywordLog.push(thing);
keywordLog.sort((a, b) => b.timestamp - a.timestamp);
@ -363,6 +375,10 @@ export default definePlugin({
this.onUpdate();
},
deleteKeyword(id) {
keywordLog = keywordLog.filter(e => e.id !== id);
this.onUpdate();
},
keywordTabBar() {
return (
@ -381,33 +397,23 @@ export default definePlugin({
const [tempLogs, setKeywordLog] = useState(keywordLog);
this.onUpdate = () => {
const newLog = [...keywordLog];
const newLog = Array.from(keywordLog);
setKeywordLog(newLog);
DataStore.set(KEYWORD_LOG_KEY, newLog.map(e => JSON.stringify(e)));
};
const onDelete = m => {
keywordLog = keywordLog.filter(e => e.id !== m.id);
this.onUpdate();
};
const messageRender = (e, t) => {
console.log(this);
e._keyword = true;
e.customRenderedContent = {
content: highlightKeywords(e.content, keywordEntries.map(e => e.regex))
};
const msg = this.renderMsg({
message: e,
gotoMessage: t,
dismissible: true
});
if (msg == null)
return [null];
msg.props.children[0].props.children.props.onClick = () => onDelete(e);
msg.props.children[1].props.children[1].props.message.customRenderedContent = {
content: highlightKeywords(e.content, keywordEntries.map(e => e.regex))
};
return [msg];
};
@ -420,7 +426,7 @@ export default definePlugin({
channel={channel}
onJump={onJump}
onFetch={() => null}
onCloseMessage={onDelete}
onCloseMessage={this.deleteKeyword}
loadMore={() => null}
messages={tempLogs}
renderEmptyState={() => null}

View file

@ -38,7 +38,7 @@ const { Spinner } = proxyLazy(() => Forms as any as {
SpinnerTypes: typeof SpinnerTypes;
});
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)") as ComponentType<any>;
const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hideAccessories");
export default definePlugin({
name: "MessageLinkTooltip",
@ -49,7 +49,7 @@ export default definePlugin({
{
find: ',className:"channelMention",children:[',
replacement: {
match: /(?<=\.jsxs\)\()(\i\.default)/,
match: /(?<=\.jsxs\)\()(\i\.\i)/,
replace: "$self.wrapComponent(arguments[0], $1)"
}
}

View file

@ -1,190 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import {
Button,
Clipboard,
GuildMemberStore,
Text,
Toasts,
UserProfileStore,
UserStore
} from "@webpack/common";
import { GuildMember } from "discord-types/general";
const SummaryItem = findComponentByCodeLazy("borderType", "showBorder", "hideDivider");
interface SavedProfile {
nick: string | null;
pronouns: string | null;
bio: string | null;
themeColors: number[] | undefined;
banner: string | undefined;
avatar: string | undefined;
profileEffectId: string | undefined;
avatarDecoration: string | undefined;
}
const savedProfile: SavedProfile = {
nick: null,
pronouns: null,
bio: null,
themeColors: undefined,
banner: undefined,
avatar: undefined,
profileEffectId: undefined,
avatarDecoration: undefined,
};
const {
setPendingAvatar,
setPendingBanner,
setPendingBio,
setPendingNickname,
setPendingPronouns,
setPendingThemeColors,
setPendingProfileEffectId,
setPendingAvatarDecoration,
}: {
setPendingAvatar: (a: string | undefined) => void;
setPendingBanner: (a: string | undefined) => void;
setPendingBio: (a: string | null) => void;
setPendingNickname: (a: string | null) => void;
setPendingPronouns: (a: string | null) => void;
setPendingThemeColors: (a: number[] | undefined) => void;
setPendingProfileEffectId: (a: string | undefined) => void;
setPendingAvatarDecoration: (a: string | undefined) => void;
} = findByPropsLazy("setPendingNickname", "setPendingPronouns");
export default definePlugin({
name: "ServerProfilesToolbox",
authors: [Devs.D3SOX],
description: "Adds a copy/paste/reset button to the server profiles editor",
patchServerProfiles({ guildId }: { guildId: string; }) {
const currentUser = UserStore.getCurrentUser();
const premiumType = currentUser.premiumType ?? 0;
const copy = () => {
const profile = UserProfileStore.getGuildMemberProfile(currentUser.id, guildId);
const nick = GuildMemberStore.getNick(guildId, currentUser.id);
const selfMember = GuildMemberStore.getMember(guildId, currentUser.id) as GuildMember & { avatarDecoration: string | undefined; };
savedProfile.nick = nick ?? "";
savedProfile.pronouns = profile.pronouns;
savedProfile.bio = profile.bio;
savedProfile.themeColors = profile.themeColors;
savedProfile.banner = profile.banner;
savedProfile.avatar = selfMember.avatar;
savedProfile.profileEffectId = profile.profileEffectId;
savedProfile.avatarDecoration = selfMember.avatarDecoration;
};
const paste = () => {
setPendingNickname(savedProfile.nick);
setPendingPronouns(savedProfile.pronouns);
if (premiumType === 2) {
setPendingBio(savedProfile.bio);
setPendingThemeColors(savedProfile.themeColors);
setPendingBanner(savedProfile.banner);
setPendingAvatar(savedProfile.avatar);
setPendingProfileEffectId(savedProfile.profileEffectId);
setPendingAvatarDecoration(savedProfile.avatarDecoration);
}
};
const reset = () => {
setPendingNickname(null);
setPendingPronouns("");
if (premiumType === 2) {
setPendingBio(null);
setPendingThemeColors([]);
setPendingBanner(undefined);
setPendingAvatar(undefined);
setPendingProfileEffectId(undefined);
setPendingAvatarDecoration(undefined);
}
};
const copyToClipboard = () => {
copy();
Clipboard.copy(JSON.stringify(savedProfile));
};
const pasteFromClipboard = async () => {
try {
const clip = await navigator.clipboard.readText();
if (!clip) {
Toasts.show({
message: "Clipboard is empty",
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
});
return;
}
const clipboardProfile: SavedProfile = JSON.parse(clip);
if (!("nick" in clipboardProfile)) {
Toasts.show({
message: "Data is not in correct format",
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
});
return;
}
Object.assign(savedProfile, JSON.parse(clip));
paste();
} catch (e) {
Toasts.show({
message: `Failed to read clipboard data: ${e}`,
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
});
}
};
return <SummaryItem title="Server Profiles Toolbox" hideDivider={false} forcedDivider>
<div style={{ display: "flex", alignItems: "center", flexDirection: "column", gap: "5px" }}>
<Text variant="text-md/normal">
Use the following buttons to mange the currently selected server
</Text>
<div style={{ display: "flex", gap: "5px" }}>
<Button onClick={copy}>
Copy profile
</Button>
<Button onClick={paste}>
Paste profile
</Button>
<Button onClick={reset}>
Reset profile
</Button>
</div>
<div style={{ display: "flex", gap: "5px" }}>
<Button onClick={copyToClipboard}>
Copy to clipboard
</Button>
<Button onClick={pasteFromClipboard}>
Paste from clipboard
</Button>
</div>
</div>
</SummaryItem>;
},
patches: [
{
find: ".PROFILE_CUSTOMIZATION_GUILD_SELECT_TITLE",
replacement: {
match: /return\(0(.{10,350})\}\)\}\)\}/,
replace: "return [(0$1})}),$self.patchServerProfiles(e)]}"
}
}
],
});

View file

@ -1,193 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { closeModal, ModalCloseButton,ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { find, findByPropsLazy } from "@webpack";
import { Button, Clickable, Forms, GuildStore, PermissionsBits, PermissionStore, Popout, SearchableSelect, showToast, Text, TextInput, Toasts, useMemo, UserStore, useState } from "@webpack/common";
import { Guild } from "discord-types/general";
import { HtmlHTMLAttributes } from "react";
import { cl, getEmojiUrl,SoundEvent } from "../utils";
export function openCloneSoundModal(item) {
const key = openModal(props =>
<ModalRoot {...props}>
<CloneSoundModal item={item} closeModal={() => closeModal(key)} />
</ModalRoot>
);
}
// Thanks https://github.com/Vendicated/Vencord/blob/ea11f2244fde469ce308f8a4e7224430be62f8f1/src/plugins/emoteCloner/index.tsx#L173-L177
const getFontSize = (s: string, small: boolean = false) => {
const sizes = [20, 20, 18, 18, 16, 14, 12];
return sizes[s.length + (small ? 1 : 0)] ?? 8;
};
function GuildAcronym({ acronym, small, style = {} }) {
return (
<Flex style={{ alignItems: "center", justifyContent: "center", overflow: "hidden", fontSize: getFontSize(acronym, small), ...style }}>
<Text>{acronym}</Text>
</Flex>
);
}
function CustomInput({ children, className = "", ...props }: HtmlHTMLAttributes<HTMLDivElement>) {
return <div className={classes(cl("clone-input"), className)} {...props}>
{children}
</div>;
}
const EmojiPicker = LazyComponent(() => find(e => e.default?.type?.render?.toString?.().includes?.(".updateNewlyAddedLastSeen)")).default);
const sounds = findByPropsLazy("uploadSound", "updateSound");
export function CloneSoundModal({ item, closeModal }: { item: SoundEvent, closeModal: () => void; }) {
const ownedGuilds = useMemo(() => {
return Object.values(GuildStore.getGuilds()).filter(guild =>
guild.ownerId === UserStore.getCurrentUser().id ||
(PermissionStore.getGuildPermissions({ id: guild.id }) & PermissionsBits.CREATE_GUILD_EXPRESSIONS) === PermissionsBits.CREATE_GUILD_EXPRESSIONS);
}, []);
const [selectedGuild, setSelectedGuild] = useState<Guild | null>(null);
const [soundName, setSoundName] = useState<string | undefined>(undefined);
const [soundEmoji, setSoundEmoji] = useState<any | undefined>(undefined);
const [loadingButton, setLoadingButton] = useState<boolean>(false);
const [show, setShow] = useState(false);
const isEmojiValid = soundEmoji?.guildId ? soundEmoji.guildId === selectedGuild?.id : true;
const styles = {
selected: { height: "24px", width: "24px" },
nonselected: { width: "36px", height: "36px", marginTop: "4px" }
};
const getStyle = key => key === "selected" ? styles.selected : styles.nonselected;
function onSelectEmoji(emoji) {
setShow(false);
setSoundEmoji(emoji);
}
return <>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Clone Sound</Text>
<ModalCloseButton onClick={closeModal} />
</Flex>
</ModalHeader>
<ModalContent>
<Forms.FormTitle className={Margins.top16}>Cloning Sound</Forms.FormTitle>
<CustomInput style={{ display: "flex", flexDirection: "row", gap: "10px", alignItems: "center" }} className={Margins.bottom16}>
<img src={getEmojiUrl(item.emoji)} width="24" height="24" />
<Text>{item.soundId}</Text>
</CustomInput>
<Forms.FormTitle required={true} aria-required="true">Add to server:</Forms.FormTitle>
<SearchableSelect
options={
ownedGuilds.map(guild => ({
label: guild.name,
value: guild
}))
}
placeholder="Select a server"
value={selectedGuild ? ({
label: selectedGuild.name,
value: selectedGuild,
key: "selected"
}) : undefined}
onChange={v => setSelectedGuild(v)}
closeOnSelect={true}
renderOptionPrefix={v => v ? (
v.value.icon ?
<img width={36} height={36} src={v.value.getIconURL(96, true)} style={{ borderRadius: "50%", ...getStyle(v.key) }} /> :
<GuildAcronym acronym={v.value.acronym} style={getStyle(v.key)} small={v.key === "selected"} />
) : null}
/>
<Flex flexDirection="row" style={{ gap: "10px", justifyContent: "space-between" }} className={classes(Margins.top16, Margins.bottom16)}>
<div style={{ flex: 1 }}>
<Forms.FormTitle required={true} aria-required="true">Sound Name</Forms.FormTitle>
<TextInput value={soundName} onChange={v => setSoundName(v)} placeholder="Sound Name" />
</div>
<div style={{ flex: 1 }}>
<Forms.FormTitle>Related Emoji</Forms.FormTitle>
<Popout
position="bottom"
align="right"
animation={Popout.Animation.NONE}
shouldShow={show}
onRequestClose={() => setShow(false)}
renderPopout={() => <EmojiPicker pickerIntention={2} channel={{ getGuildId: () => selectedGuild?.id }} onSelectEmoji={onSelectEmoji} />}
>
{() => (
<Clickable onClick={() => setShow(v => !v)}>
<CustomInput style={{ display: "flex", flexDirection: "row", gap: "10px", alignItems: "center", cursor: "pointer" }}>
{soundEmoji ?
<>
<img src={getEmojiUrl({ name: soundEmoji.surrogates, id: soundEmoji.id })} width="24" height="24" style={{ cursor: "pointer" }} />
<Text style={{ color: "var(--text-muted)", cursor: "pointer" }}>:{soundEmoji.name ? soundEmoji.name.split("~")[0] : soundEmoji.uniqueName}:</Text>
</> :
<>
<img src={getEmojiUrl({ name: "😊" })} width="24" height="24" style={{ filter: "grayscale(100%)", cursor: "pointer" }} />
<Text style={{ color: "var(--text-muted)", cursor: "pointer" }}>Click to Select</Text>
</>
}
</CustomInput>
</Clickable>
)}
</Popout>
</div>
</Flex>
{!isEmojiValid && <Forms.FormText style={{ color: "var(--text-danger)" }} className={Margins.bottom16}>You can't use that emoji in that server</Forms.FormText>}
<Button onClick={() => {
setLoadingButton(true);
fetch(`https://cdn.discordapp.com/soundboard-sounds/${item.soundId}`).then(function (response) {
if (!response.body) {
setLoadingButton(false);
showToast("Error fetching the sound", Toasts.Type.FAILURE);
return;
}
response.body.getReader().read().then(function (result) {
if (!result.value) {
setLoadingButton(false);
showToast("Error reading the sound content", Toasts.Type.FAILURE);
return;
}
return btoa(String.fromCharCode(...result.value));
}).then(function (b64) {
sounds.uploadSound({
guildId: selectedGuild?.id,
name: soundName,
sound: `data:audio/ogg;base64,${b64}`,
...(soundEmoji.id ? { emojiId: soundEmoji.id } : { emojiName: soundEmoji.surrogates }),
volume: 1
}).then(() => {
showToast(`Sound added to ${selectedGuild?.name}`, Toasts.Type.SUCCESS);
closeModal();
}).catch(() => {
setLoadingButton(false);
showToast("Error while adding sound", Toasts.Type.FAILURE);
});
});
}).catch(e => {
setLoadingButton(false);
showToast("Error fetching the sound", Toasts.Type.FAILURE);
return;
});
}}
disabled={(!(selectedGuild && soundName && isEmojiValid)) || loadingButton}
size={Button.Sizes.MEDIUM}
style={{ width: "100%" }}
className={Margins.bottom16}>Add to Server</Button>
</ModalContent>
</>;
}

View file

@ -13,8 +13,7 @@ import { Button, Clickable, ContextMenuApi, FluxDispatcher, Forms, Menu, Text, T
import { User } from "discord-types/general";
import { clearLoggedSounds, getLoggedSounds } from "../store";
import { addListener, AvatarStyles, cl, downloadAudio, getEmojiUrl, getSoundboardVolume, playSound, removeListener, SoundLogEntry, UserSummaryItem } from "../utils";
import { openCloneSoundModal } from "./CloneSoundModal";
import { addListener, AvatarStyles, cl, downloadAudio, getEmojiUrl, playSound, removeListener, SoundLogEntry, UserSummaryItem } from "../utils";
import { openMoreUsersModal } from "./MoreUsersModal";
import { openUserModal } from "./UserModal";
@ -100,13 +99,6 @@ export default function SoundBoardLog({ data, closeModal }) {
navId="soundboardlogger-sound-menu"
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
>
<Menu.MenuGroup label="Extra buttons">
<Menu.MenuItem
id={label("clone")}
label="Clone sound"
action={() => openCloneSoundModal(item)}
/>
</Menu.MenuGroup>
</Menu.Menu>
);
}
@ -164,7 +156,7 @@ export default function SoundBoardLog({ data, closeModal }) {
<Flex flexDirection="row" className={cl("sound-buttons")}>
<Button color={Button.Colors.PRIMARY} size={Button.Sizes.SMALL} onClick={() => downloadAudio(item.soundId)}>Download</Button>
<Button color={Button.Colors.GREEN} size={Button.Sizes.SMALL} onClick={() => copyWithToast(item.soundId, "ID copied to clipboard!")}>Copy ID</Button>
<Tooltip text={`Soundboard volume: ${Math.floor(getSoundboardVolume())}%`}>
<Tooltip text={"Soundboard volume: currently broken"}>
{({ onMouseEnter, onMouseLeave }) =>
<Button color={Button.Colors.BRAND} size={Button.Sizes.SMALL} onClick={() => playSound(item.soundId)} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>Play Sound</Button>
}

View file

@ -39,14 +39,9 @@ export function getEmojiUrl(emoji) {
return emoji.id ? `https://cdn.discordapp.com/emojis/${emoji.id}.png?size=32` : getURL(emoji.name);
}
const v1 = findByPropsLazy("amplitudeToPerceptual");
const v2 = findByPropsLazy("getAmplitudinalSoundboardVolume");
export const getSoundboardVolume = () => v1.amplitudeToPerceptual(v2.getAmplitudinalSoundboardVolume());
export const playSound = id => {
const audio = new Audio(`https://cdn.discordapp.com/soundboard-sounds/${id}`);
audio.volume = getSoundboardVolume() / 100;
audio.volume = 75 / 100;
audio.play();
};

View file

@ -62,9 +62,6 @@ function Watching({ userIds, guildId }: WatchingProps): JSX.Element {
}
const ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore");
const { encodeStreamKey }: {
encodeStreamKey: (any) => string;
} = findByPropsLazy("encodeStreamKey");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@ -80,8 +77,8 @@ export default definePlugin({
{
find: ".Masks.STATUS_SCREENSHARE,width:32",
replacement: {
match: /default:function\(\)\{return (\i)\}/,
replace: "default:function(){return $self.component({OriginalComponent:$1})}"
match: /(\i):function\(\)\{return (\i)\}/,
replace: "$1:function(){return $self.component({OriginalComponent:$2})}"
}
},
{
@ -98,7 +95,7 @@ export default definePlugin({
if (!stream) return <div {...props}>{props.children}</div>;
const userIds = ApplicationStreamingStore.getViewerIds(encodeStreamKey(stream));
const userIds = ApplicationStreamingStore.getViewerIds(stream);
let missingUsers = 0;
const users = userIds.map(id => UserStore.getUser(id)).filter(user => Boolean(user) ? true : (missingUsers += 1, false));
@ -160,7 +157,7 @@ export default definePlugin({
component: function ({ OriginalComponent }) {
return (props: any) => {
const stream = useStateFromStores([ApplicationStreamingStore], () => ApplicationStreamingStore.getCurrentUserActiveStream());
const viewers = ApplicationStreamingStore.getViewerIds(encodeStreamKey(stream));
const viewers = ApplicationStreamingStore.getViewerIds(stream);
return <Tooltip text={<Watching userIds={viewers} guildId={stream.guildId} />}>
{({ onMouseEnter, onMouseLeave }) => (
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>

View file

@ -20,7 +20,7 @@ export default definePlugin({
{
find: ".Messages.SUPPRESS_ALL_EMBEDS",
replacement: {
match: /case (\i\.MessageEmbedTypes\.VIDEO):(case \i\.MessageEmbedTypes\.\i:)*break;default:(\i)=(?:(this\.renderDescription)\(\))\}/,
match: /case (\i\.\i\.VIDEO):(case \i\.\i\.\i:)*break;default:(\i)=(?:(this\.renderDescription)\(\))\}/,
replace: "$2 break; case $1: $3 = $self.ToggleableDescriptionWrapper({ embed: this.props.embed, original: $4.bind(this) }); break; default: $3 = $4() }"
}
}

View file

@ -65,15 +65,15 @@ export default definePlugin({
}
},
{
find: "Messages.USER_PROFILE_MUTUAL_GUILDS_PLACEHOLDER).with",
find: /Messages\.USER_PROFILE_MUTUAL_GUILDS_PLACEHOLDER\)\.with\(0,\(\)=>\i\.\i\.Messages\.USER_PROFILE_NO_MUTUAL_SERVERS/,
group: true,
replacement: [
{
match: /(user:(\i),.+?=\i,)(.+?)(\i\.push)(.+?UserProfileSections\.MUTUAL_GUILDS,text:.{0,250}}\)\)}\))/,
match: /(user:(\i),.+?=\i,)(.+?)(\i\.push)(.+?\i\.MUTUAL_GUILDS,text:.{0,250}}\)\)}\))/,
replace: '$1vencordMutualGroupsTabLabel=$self.useGDMCount($2.id),$3$5,$4({section:"MUTUAL_GDMS",text:vencordMutualGroupsTabLabel})'
},
{
match: /(?<=(\i)===\i\.UserProfileSections\.MUTUAL_GUILDS?.{0,150}\}\):)/,
match: /(?<=(\i)===\i\.\i\i\.MUTUAL_GUILDS?.{0,150}\}\):)/,
replace: '$1==="MUTUAL_GDMS"?$self.renderMutualGDMs(arguments[0]):'
},
]