Merge branch 'dev' into customFolderIcons

This commit is contained in:
sadan4 2024-09-14 15:16:10 -04:00 committed by GitHub
commit 50ce282284
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 386 additions and 264 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.9.9", "version": "1.10.1",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Logger } from "@utils/Logger";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import { sendBotMessage } from "./commandHelpers"; import { sendBotMessage } from "./commandHelpers";
@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder;
export const _init = function (cmds: Command[]) { export const _init = function (cmds: Command[]) {
try { try {
BUILT_IN = cmds; BUILT_IN = cmds;
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0]; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0]; RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
} catch (e) { } catch (e) {
console.error("Failed to load CommandsApi"); new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
} }
return cmds; return cmds;
} as never; } as never;
@ -138,6 +139,8 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
throw new Error(`Command '${command.name}' already exists.`); throw new Error(`Command '${command.name}' already exists.`);
command.isVencordCommand = true; command.isVencordCommand = true;
command.untranslatedName ??= command.name;
command.untranslatedDescription ??= command.description;
command.id ??= `-${BUILT_IN.length + 1}`; command.id ??= `-${BUILT_IN.length + 1}`;
command.applicationId ??= "-1"; // BUILT_IN; command.applicationId ??= "-1"; // BUILT_IN;
command.type ??= ApplicationCommandType.CHAT_INPUT; command.type ??= ApplicationCommandType.CHAT_INPUT;

View file

@ -93,8 +93,10 @@ export interface Command {
isVencordCommand?: boolean; isVencordCommand?: boolean;
name: string; name: string;
untranslatedName?: string;
displayName?: string; displayName?: string;
description: string; description: string;
untranslatedDescription?: string;
displayDescription?: string; displayDescription?: string;
options?: Option[]; options?: Option[];

View file

@ -1,3 +0,0 @@
[class*="profileBadges"] {
flex: none;
}

View file

@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./fixBadgeOverflow.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton"; import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
@ -79,7 +77,7 @@ export default definePlugin({
replace: "...$1.props,$& $1.image??" replace: "...$1.props,$& $1.image??"
}, },
{ {
match: /(?<=text:(\i)\.description,.{0,50})children:/, match: /(?<=text:(\i)\.description,.{0,200})children:/,
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
}, },
// conditionally override their onClick with badge.onClick if it exists // conditionally override their onClick with badge.onClick if it exists

View file

@ -34,7 +34,7 @@ export default definePlugin({
{ {
find: "Messages.SERVERS,children", find: "Messages.SERVERS,children",
replacement: { replacement: {
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/, match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/,
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)" replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
} }
} }

View file

@ -48,7 +48,7 @@ export default definePlugin({
}, },
}, },
{ {
find: ".METRICS,", find: ".METRICS",
replacement: [ replacement: [
{ {
match: /this\._intervalId=/, match: /this\._intervalId=/,

View file

@ -0,0 +1,7 @@
# AccountPanelServerProfile
Right click your account panel in the bottom left to view your profile in the current server
![](https://github.com/user-attachments/assets/3228497d-488f-479c-93d2-a32ccdb08f0f)
![](https://github.com/user-attachments/assets/6fc45363-d95f-4810-812f-2f9fb28b41b5)

View file

@ -0,0 +1,134 @@
/*
* 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 { getCurrentChannel } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
import { User } from "discord-types/general";
interface UserProfileProps {
popoutProps: Record<string, any>;
currentUser: User;
originalPopout: () => React.ReactNode;
}
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
const styles = findByPropsLazy("accountProfilePopoutWrapper");
let openAlternatePopout = false;
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
return (
<Menu.Menu
navId="vc-ap-server-profile"
onClose={ContextMenuApi.closeContextMenu}
>
<Menu.MenuItem
id="vc-ap-view-alternate-popout"
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
disabled={getCurrentChannel()?.getGuildId() == null}
action={e => {
openAlternatePopout = true;
accountPanelRef.current?.props.onMouseDown();
accountPanelRef.current?.props.onClick(e);
}}
/>
<Menu.MenuCheckboxItem
id="vc-ap-prioritize-server-profile"
label="Prioritize Server Profile"
checked={prioritizeServerProfile}
action={() => settings.store.prioritizeServerProfile = !prioritizeServerProfile}
/>
</Menu.Menu>
);
}, { noop: true });
const settings = definePluginSettings({
prioritizeServerProfile: {
type: OptionType.BOOLEAN,
description: "Prioritize Server Profile when left clicking your account panel",
default: false
}
});
export default definePlugin({
name: "AccountPanelServerProfile",
description: "Right click your account panel in the bottom left to view your profile in the current server",
authors: [Devs.Nuckyz, Devs.relitrix],
settings,
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
group: true,
replacement: [
{
match: /(?<=\.SIZE_32\)}\);)/,
replace: "$self.useAccountPanelRef();"
},
{
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
},
{
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
replace: "$&onRequestClose:$self.onPopoutClose,"
},
{
match: /(?<=.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
}
]
}
],
get accountPanelRef() {
return accountPanelRef;
},
useAccountPanelRef() {
useEffect(() => () => {
accountPanelRef.current = null;
}, []);
return (accountPanelRef = useRef(null));
},
openAccountPanelContextMenu(event: React.UIEvent) {
ContextMenuApi.openContextMenu(event, AccountPanelContextMenu);
},
onPopoutClose() {
openAlternatePopout = false;
},
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
if (
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
) {
return originalPopout();
}
const currentChannel = getCurrentChannel();
if (currentChannel?.getGuildId() == null) {
return originalPopout();
}
return (
<div className={styles.accountProfilePopoutWrapper}>
<UserProfile {...popoutProps} userId={currentUser.id} guildId={currentChannel.getGuildId()} channelId={currentChannel.id} />
</div>
);
}, { noop: true })
});

View file

@ -120,7 +120,7 @@ const settings = definePluginSettings({
stateString: { stateString: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Activity state format string", description: "Activity state format string",
default: "{artist}" default: "{artist} · {album}"
}, },
largeImageType: { largeImageType: {
type: OptionType.SELECT, type: OptionType.SELECT,

View file

@ -11,37 +11,11 @@ import type { TrackData } from ".";
const exec = promisify(execFile); const exec = promisify(execFile);
// function exec(file: string, args: string[] = []) {
// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });
// let stdout: string | null = null;
// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
// let stderr: string | null = null;
// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });
// process.on("exit", code => { resolve({ code, stdout, stderr }); });
// process.on("error", err => reject(err));
// });
// }
async function applescript(cmds: string[]) { async function applescript(cmds: string[]) {
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat()); const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
return stdout; return stdout;
} }
function makeSearchUrl(type: string, query: string) {
const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
url.searchParams.set("types", type);
url.searchParams.set("limit", "1");
url.searchParams.set("term", query);
return url;
}
const requestOptions: RequestInit = {
headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
};
interface RemoteData { interface RemoteData {
appleMusicLink?: string, appleMusicLink?: string,
songLink?: string, songLink?: string,
@ -51,6 +25,24 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
let cachedToken: string | undefined = undefined;
const getToken = async () => {
if (cachedToken) return cachedToken;
const html = await fetch("https://music.apple.com/").then(r => r.text());
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");
const bundle = await fetch(bundleUrl).then(r => r.text());
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![1];
cachedToken = token;
return token;
};
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) { async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) { if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data; if ("data" in cachedRemoteData) return cachedRemoteData.data;
@ -58,21 +50,39 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
} }
try { try {
const [songData, artistData] = await Promise.all([ const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()), dataUrl.searchParams.set("platform", "web");
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json()) dataUrl.searchParams.set("l", "en-US");
]); dataUrl.searchParams.set("limit", "1");
dataUrl.searchParams.set("with", "serverBubbles");
dataUrl.searchParams.set("types", "songs");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists");
const appleMusicLink = songData?.songs?.data[0]?.attributes.url; const token = await getToken();
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); const songData = await fetch(dataUrl, {
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512"); headers: {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"authorization": `Bearer ${token}`,
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"origin": "https://music.apple.com",
},
})
.then(r => r.json())
.then(data => data.results.song.data[0]);
cachedRemoteData = { cachedRemoteData = {
id, id,
data: { appleMusicLink, songLink, albumArtwork, artistArtwork } data: {
appleMusicLink: songData.attributes.url,
songLink: `https://song.link/i/${songData.id}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
}
}; };
return cachedRemoteData.data; return cachedRemoteData.data;
} catch (e) { } catch (e) {
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e); console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);

View file

@ -0,0 +1,11 @@
# Better Folders
Better Folders offers a variety of options to improve your folder experience
Always show the folder icon, regardless of if the folder is open or not
Only have one folder open at a time
Open folders in a sidebar:
![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947)

View file

@ -30,9 +30,9 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded MoreThanOneFolderExpanded
} }
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null; let lastGuildId = null as string | null;
@ -118,22 +118,22 @@ export default definePlugin({
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{ {
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)` replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)`
}, },
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
{ {
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/, match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))" replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
}, },
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children // If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
{ {
match: /unreadMentionsIndicatorBottom,.+?}\)\]/, match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))" replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))"
}, },
// Export the isBetterFolders variable to the folders component // Export the isBetterFolders variable to the folders component
{ {
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/, match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/,
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
} }
] ]
}, },
@ -167,31 +167,31 @@ export default definePlugin({
{ {
predicate: () => settings.store.keepIcons, predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/, match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};` replace: (_, isExpanded) => `${isExpanded}=!!arguments[0]?.isBetterFolders&&${isExpanded};`
}, },
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/, match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&" replace: "$self.shouldShowTransition(arguments[0])&&"
}, },
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/, match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:` replace: (m, isExpanded) => `${m}$self.shouldRenderContents(arguments[0],${isExpanded})?null:`
}, },
{ {
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/, match: /(?<=\.wrapper,children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&" replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
}, },
{ {
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/, match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:" replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
} }
] ]
}, },
@ -201,7 +201,7 @@ export default definePlugin({
replacement: { replacement: {
// Render the Better Folders sidebar // Render the Better Folders sidebar
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/, match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
replace: ",$self.FolderSideBar($1)" replace: ",$self.FolderSideBar({...$1})"
} }
}, },
{ {
@ -306,7 +306,20 @@ export default definePlugin({
} }
}, },
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />, shouldShowTransition(props: any) {
// Pending guilds
if (props?.folderNode?.id === 1) return true;
closeFolders return !!props?.isBetterFolders;
},
shouldRenderContents(props: any, isExpanded: boolean) {
// Pending guilds
if (props?.folderNode?.id === 1) return false;
return !props?.isBetterFolders && isExpanded;
},
FolderSideBar,
closeFolders,
}); });

View file

@ -26,12 +26,11 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color"); const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
@ -436,8 +435,8 @@ export default definePlugin({
<Forms.FormDivider className={Margins.top8} /> <Forms.FormDivider className={Margins.top8} />
<div style={{ width: "284px", ...profileThemeStyle }}> <div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
{activity[0] && <ActivityComponent activity={activity[0]} className={ActivityClassName.activity} channelId={SelectedChannelStore.getChannelId()} {activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())} guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
application={{ id: settings.store.appID }} application={{ id: settings.store.appID }}
user={UserStore.getCurrentUser()} />} user={UserStore.getCurrentUser()} />}

View file

@ -46,7 +46,7 @@ const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/;
async function embedDidMount(this: Component<Props>) { async function embedDidMount(this: Component<Props>) {
try { try {
const { embed } = this.props; const { embed } = this.props;
const { replaceElements } = settings.store; const { replaceElements, dearrowByDefault } = settings.store;
if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return; if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;
@ -63,18 +63,22 @@ async function embedDidMount(this: Component<Props>) {
if (!hasTitle && !hasThumb) return; if (!hasTitle && !hasThumb) return;
embed.dearrow = { embed.dearrow = {
enabled: true enabled: dearrowByDefault
}; };
if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) {
embed.dearrow.oldTitle = embed.rawTitle; const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2");
}
embed.dearrow.oldTitle = dearrowByDefault ? embed.rawTitle : replacementTitle;
if (dearrowByDefault) embed.rawTitle = replacementTitle;
}
if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) {
embed.dearrow.oldThumb = embed.thumbnail.proxyURL; const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL;
if (dearrowByDefault) embed.thumbnail.proxyURL = replacementProxyURL;
} }
this.forceUpdate(); this.forceUpdate();
@ -96,6 +100,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")} className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")}
onClick={() => { onClick={() => {
const { enabled, oldThumb, oldTitle } = embed.dearrow; const { enabled, oldThumb, oldTitle } = embed.dearrow;
settings.store.dearrowByDefault = !enabled;
embed.dearrow.enabled = !enabled; embed.dearrow.enabled = !enabled;
if (oldTitle) { if (oldTitle) {
embed.dearrow.oldTitle = embed.rawTitle; embed.dearrow.oldTitle = embed.rawTitle;
@ -153,6 +158,12 @@ const settings = definePluginSettings({
{ label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly },
{ label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly },
], ],
},
dearrowByDefault: {
description: "Dearrow videos automatically",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: false
} }
}); });

View file

@ -7,16 +7,15 @@
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Heading, RelationshipStore, Text } from "@webpack/common"; import { RelationshipStore, Text } from "@webpack/common";
const containerWrapper = findByPropsLazy("memberSinceWrapper"); const containerWrapper = findByPropsLazy("memberSinceWrapper");
const container = findByPropsLazy("memberSince"); const container = findByPropsLazy("memberSince");
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
const locale = findByPropsLazy("getLocale"); const locale = findByPropsLazy("getLocale");
const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2); const Section = findComponentByCodeLazy('"auto":"smooth"', ".section");
export default definePlugin({ export default definePlugin({
name: "FriendsSince", name: "FriendsSince",
@ -28,7 +27,7 @@ export default definePlugin({
find: ".PANEL}),nicknameIcons", find: ".PANEL}),nicknameIcons",
replacement: { replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/, match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/,
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})" replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})"
} }
}, },
// User Profile Modal // User Profile Modal
@ -36,34 +35,19 @@ export default definePlugin({
find: "action:\"PRESS_APP_CONNECTION\"", find: "action:\"PRESS_APP_CONNECTION\"",
replacement: { replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false})," replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false}),"
} }
} }
], ],
getFriendSince(userId: string) { FriendsSinceComponent: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
try {
if (!RelationshipStore.isFriend(userId)) return null;
return RelationshipStore.getSince(userId);
} catch (err) {
new Logger("FriendsSince").error(err);
return null;
}
},
friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
if (!RelationshipStore.isFriend(userId)) return null; if (!RelationshipStore.isFriend(userId)) return null;
const friendsSince = RelationshipStore.getSince(userId); const friendsSince = RelationshipStore.getSince(userId);
if (!friendsSince) return null; if (!friendsSince) return null;
return ( return (
<section className={section.section}> <Section heading="Friends Since">
<Heading variant="text-xs/semibold" style={isSidebar ? {} : { color: "var(--header-secondary)" }}>
Friends Since
</Heading>
{ {
isSidebar ? ( isSidebar ? (
<Text variant="text-sm/normal"> <Text variant="text-sm/normal">
@ -91,8 +75,7 @@ export default definePlugin({
</div> </div>
) )
} }
</Section>
</section>
); );
}, { noop: true }), }, { noop: true }),
}); });

View file

@ -266,7 +266,7 @@ export default definePlugin({
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
} }
}, },
// Discord has 3 different components for activities. Currently, the last is the one being used // Discord has 2 different components for activities. Currently, the last is the one being used
{ {
find: ".activityTitleText,variant", find: ".activityTitleText,variant",
replacement: { replacement: {
@ -274,13 +274,6 @@ export default definePlugin({
replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
}, },
}, },
{
find: ".activityCardDetails,children",
replacement: {
match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/,
replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
}
},
{ {
find: ".promotedLabelWrapperNonBanner,children", find: ".promotedLabelWrapperNonBanner,children",
replacement: { replacement: {

View file

@ -66,14 +66,14 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
patch.replacement = [patch.replacement]; patch.replacement = [patch.replacement];
} }
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
if (IS_REPORTER) { if (IS_REPORTER) {
patch.replacement.forEach(r => { patch.replacement.forEach(r => {
delete r.predicate; delete r.predicate;
}); });
} }
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
patches.push(patch); patches.push(patch);
} }

View file

@ -14,7 +14,7 @@ import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id; const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id;
const totalCount = useStateFromStores( const totalCount = useStateFromStores(
[GuildMemberCountStore], [GuildMemberCountStore],
@ -33,7 +33,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
const threadGroups = useStateFromStores( const threadGroups = useStateFromStores(
[ThreadMemberListStore], [ThreadMemberListStore],
() => ThreadMemberListStore.getMemberListSections(currentChannel.id) () => ThreadMemberListStore.getMemberListSections(currentChannel?.id)
); );
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {

View file

@ -15,8 +15,8 @@ export const OnlineMemberCountStore = proxyLazy(() => {
const onlineMemberMap = new Map<string, number>(); const onlineMemberMap = new Map<string, number>();
class OnlineMemberCountStore extends Flux.Store { class OnlineMemberCountStore extends Flux.Store {
getCount(guildId: string) { getCount(guildId?: string) {
return onlineMemberMap.get(guildId); return onlineMemberMap.get(guildId!);
} }
async _ensureCount(guildId: string) { async _ensureCount(guildId: string) {
@ -25,8 +25,8 @@ export const OnlineMemberCountStore = proxyLazy(() => {
await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
} }
ensureCount(guildId: string) { ensureCount(guildId?: string) {
if (onlineMemberMap.has(guildId)) return; if (!guildId || onlineMemberMap.has(guildId)) return;
preloadQueue.push(() => preloadQueue.push(() =>
this._ensureCount(guildId) this._ensureCount(guildId)

View file

@ -28,12 +28,12 @@ import { FluxStore } from "@webpack/types";
import { MemberCount } from "./MemberCount"; import { MemberCount } from "./MemberCount";
export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId?: string): number | null; };
export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; getProps(guildId?: string, channelId?: string): { groups: { count: number; id: string; }[]; };
}; };
export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & { export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & {
getMemberListSections(channelId: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
}; };

View file

@ -9,3 +9,8 @@
.vc-mentionAvatars-role-icon { .vc-mentionAvatars-role-icon {
margin: 0 2px 0.2rem 4px; margin: 0 2px 0.2rem 4px;
} }
/** don't display inside the ServerInfo modal owner mention */
.vc-gp-owner .vc-mentionAvatars-icon {
display: none;
}

View file

@ -62,16 +62,7 @@ export default definePlugin({
replace: "return 0;" replace: "return 0;"
} }
}, },
// New message requests hook // Message requests hook
{
find: 'location:"use-message-requests-count"',
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /getNonChannelAckId\(\i\.\i\.MESSAGE_REQUESTS\).+?return /,
replace: "$&0;"
}
},
// Old message requests hook
{ {
find: "getMessageRequestsCount(){", find: "getMessageRequestsCount(){",
predicate: () => settings.store.hideMessageRequestsCount, predicate: () => settings.store.hideMessageRequestsCount,

View file

@ -36,7 +36,7 @@ export default definePlugin({
} }
], ],
shouldSkip(guildId: string, emoji: any) { shouldSkip(guildId: string, emoji: any) {
if (emoji.type !== "GUILD_EMOJI") { if (emoji.type !== 1) {
return false; return false;
} }
if (settings.store.shownEmojis === "onlyUnicode") { if (settings.store.shownEmojis === "onlyUnicode") {

View file

@ -33,7 +33,7 @@ interface URLReplacementRule {
// Do not forget to add protocols to the ALLOWED_PROTOCOLS constant // Do not forget to add protocols to the ALLOWED_PROTOCOLS constant
const UrlReplacementRules: Record<string, URLReplacementRule> = { const UrlReplacementRules: Record<string, URLReplacementRule> = {
spotify: { spotify: {
match: /^https:\/\/open\.spotify\.com\/(?:intl-[a-z]{2}\/)?(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/, match: /^https:\/\/open\.spotify\.com\/(?:intl-[a-z]{2}\/)?(track|album|artist|playlist|user|episode|prerelease)\/(.+)(?:\?.+?)?$/,
replace: (_, type, id) => `spotify://${type}/${id}`, replace: (_, type, id) => `spotify://${type}/${id}`,
description: "Open Spotify links in the Spotify app", description: "Open Spotify links in the Spotify app",
shortlinkMatch: /^https:\/\/spotify\.link\/.+$/, shortlinkMatch: /^https:\/\/spotify\.link\/.+$/,

View file

@ -57,7 +57,7 @@ export default definePlugin({
}, },
{ {
match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/, match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/,
replace: '$&+vcHasPendingPronouns?"":` (${vcPronounSource})`' replace: '$&+(vcHasPendingPronouns?"":` (${vcPronounSource})`)'
}, },
{ {
match: /(\.pronounsText.+?children:)(\i)/, match: /(\.pronounsText.+?children:)(\i)/,

View file

@ -22,12 +22,13 @@ import { useForceUpdater } from "@utils/react";
import { Paginator, Text, useRef, useState } from "@webpack/common"; import { Paginator, Text, useRef, useState } from "@webpack/common";
import { Auth } from "../auth"; import { Auth } from "../auth";
import { ReviewType } from "../entities";
import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { cl } from "../utils"; import { cl } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) { function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) {
const [data, setData] = useState<Response>(); const [data, setData] = useState<Response>();
const [signal, refetch] = useForceUpdater(true); const [signal, refetch] = useForceUpdater(true);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@ -58,6 +59,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod
onFetchReviews={setData} onFetchReviews={setData}
scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })} scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })}
hideOwnReview hideOwnReview
type={type}
/> />
</div> </div>
</ModalContent> </ModalContent>
@ -95,7 +97,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod
); );
} }
export function openReviewsModal(discordId: string, name: string) { export function openReviewsModal(discordId: string, name: string, type: ReviewType) {
const modalKey = "vc-rdb-modal-" + Date.now(); const modalKey = "vc-rdb-modal-" + Date.now();
openModal(props => ( openModal(props => (
@ -104,6 +106,7 @@ export function openReviewsModal(discordId: string, name: string) {
modalProps={props} modalProps={props}
discordId={discordId} discordId={discordId}
name={name} name={name}
type={type}
/> />
), { modalKey }); ), { modalKey });
} }

View file

@ -21,7 +21,7 @@ import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpa
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Auth, authorize } from "../auth"; import { Auth, authorize } from "../auth";
import { Review } from "../entities"; import { Review, ReviewType } from "../entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { settings } from "../settings"; import { settings } from "../settings";
import { cl, showToast } from "../utils"; import { cl, showToast } from "../utils";
@ -45,6 +45,7 @@ interface Props extends UserProps {
page?: number; page?: number;
scrollToTop?(): void; scrollToTop?(): void;
hideOwnReview?: boolean; hideOwnReview?: boolean;
type: ReviewType;
} }
export default function ReviewsView({ export default function ReviewsView({
@ -56,6 +57,7 @@ export default function ReviewsView({
page = 1, page = 1,
showInput = false, showInput = false,
hideOwnReview = false, hideOwnReview = false,
type,
}: Props) { }: Props) {
const [signal, refetch] = useForceUpdater(true); const [signal, refetch] = useForceUpdater(true);
@ -80,6 +82,7 @@ export default function ReviewsView({
reviews={reviewData!.reviews} reviews={reviewData!.reviews}
hideOwnReview={hideOwnReview} hideOwnReview={hideOwnReview}
profileId={discordId} profileId={discordId}
type={type}
/> />
{showInput && ( {showInput && (
@ -94,7 +97,7 @@ export default function ReviewsView({
); );
} }
function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) { function ReviewList({ refetch, reviews, hideOwnReview, profileId, type }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; type: ReviewType; }) {
const myId = UserStore.getCurrentUser().id; const myId = UserStore.getCurrentUser().id;
return ( return (
@ -111,7 +114,7 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch():
{reviews?.length === 0 && ( {reviews?.length === 0 && (
<Forms.FormText className={cl("placeholder")}> <Forms.FormText className={cl("placeholder")}>
Looks like nobody reviewed this user yet. You could be the first! Looks like nobody reviewed this {type === ReviewType.User ? "user" : "server"} yet. You could be the first!
</Forms.FormText> </Forms.FormText>
)} )}
</div> </div>

View file

@ -30,7 +30,7 @@ import { Guild, User } from "discord-types/general";
import { Auth, initAuth, updateAuth } from "./auth"; import { Auth, initAuth, updateAuth } from "./auth";
import { openReviewsModal } from "./components/ReviewModal"; import { openReviewsModal } from "./components/ReviewModal";
import { NotificationType } from "./entities"; import { NotificationType, ReviewType } from "./entities";
import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
import { showToast } from "./utils"; import { showToast } from "./utils";
@ -44,7 +44,7 @@ const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { gu
label="View Reviews" label="View Reviews"
id="vc-rdb-server-reviews" id="vc-rdb-server-reviews"
icon={OpenExternalIcon} icon={OpenExternalIcon}
action={() => openReviewsModal(guild.id, guild.name)} action={() => openReviewsModal(guild.id, guild.name, ReviewType.Server)}
/> />
); );
}; };
@ -56,7 +56,7 @@ const userContextPatch: NavContextMenuPatchCallback = (children, { user }: { use
label="View Reviews" label="View Reviews"
id="vc-rdb-user-reviews" id="vc-rdb-user-reviews"
icon={OpenExternalIcon} icon={OpenExternalIcon}
action={() => openReviewsModal(user.id, user.username)} action={() => openReviewsModal(user.id, user.username, ReviewType.User)}
/> />
); );
}; };
@ -157,7 +157,7 @@ export default definePlugin({
return ( return (
<TooltipContainer text="View Reviews"> <TooltipContainer text="View Reviews">
<Button <Button
onClick={() => openReviewsModal(user.id, user.username)} onClick={() => openReviewsModal(user.id, user.username, ReviewType.User)}
look={Button.Looks.FILLED} look={Button.Looks.FILLED}
size={Button.Sizes.NONE} size={Button.Sizes.NONE}
color={RoleButtonClasses.bannerColor} color={RoleButtonClasses.bannerColor}

View file

@ -57,7 +57,7 @@ export default definePlugin({
patches: [ patches: [
// Chat Mentions // Chat Mentions
{ {
find: 'location:"UserMention', find: ".USER_MENTION)",
replacement: [ replacement: [
{ {
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,

View file

@ -4,21 +4,38 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({
onlySnow: {
type: OptionType.BOOLEAN,
description: "Only play the Snow Halation Theme",
default: false,
restartNeeded: true
}
});
// NOTE - Ultimately should probably be turned into a ringtone picker plugin
export default definePlugin({ export default definePlugin({
name: "SecretRingToneEnabler", name: "SecretRingToneEnabler",
description: "Always play the secret version of the discord ringtone (except during special ringtone events)", description: "Always play the secret version of the discord ringtone (except during special ringtone events)",
authors: [Devs.AndrewDLO, Devs.FieryFlames], authors: [Devs.AndrewDLO, Devs.FieryFlames, Devs.RamziAH],
settings,
patches: [ patches: [
{ {
find: '"call_ringing_beat"', find: '"call_ringing_beat"',
replacement: { replacement: [
{
match: /500!==\i\(\)\.random\(1,1e3\)/, match: /500!==\i\(\)\.random\(1,1e3\)/,
replace: "false", replace: "false"
}
}, },
], {
predicate: () => settings.store.onlySnow,
match: /"call_ringing_beat",/,
replace: ""
}
]
}
]
}); });

View file

@ -48,7 +48,7 @@ export default definePlugin({
find: ".Messages.FRIEND_REQUEST_CANCEL", find: ".Messages.FRIEND_REQUEST_CANCEL",
replacement: { replacement: {
predicate: () => settings.store.showDates, predicate: () => settings.store.showDates,
match: /subText:(\i)(?=,className:\i\.userInfo}\))(?<=user:(\i).+?)/, match: /subText:(\i)(?<=user:(\i).+?)/,
replace: (_, subtext, user) => `subText:$self.makeSubtext(${subtext},${user})` replace: (_, subtext, user) => `subText:$self.makeSubtext(${subtext},${user})`
} }
}], }],
@ -66,7 +66,7 @@ export default definePlugin({
makeSubtext(text: string, user: User) { makeSubtext(text: string, user: User) {
const since = this.getSince(user); const since = this.getSince(user);
return ( return (
<Flex flexDirection="row" style={{ gap: 0, flexWrap: "wrap", lineHeight: "0.9rem" }}> <Flex flexDirection="column" style={{ gap: 0, flexWrap: "wrap", lineHeight: "0.9rem" }}>
<span>{text}</span> <span>{text}</span>
{!isNaN(since.getTime()) && <span>Received &mdash; {since.toDateString()}</span>} {!isNaN(since.getTime()) && <span>Received &mdash; {since.toDateString()}</span>}
</Flex> </Flex>

View file

@ -1,5 +0,0 @@
# TimeBarAllActivities
Adds the Spotify time bar to all activities if they have start and end timestamps.
![](https://github.com/user-attachments/assets/9fbbe33c-8218-43c9-8b8d-f907a4e809fe)

View file

@ -1,76 +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 { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
interface Activity {
timestamps?: ActivityTimestamps;
}
interface ActivityTimestamps {
start?: string;
end?: string;
}
const ActivityTimeBar = findComponentByCodeLazy<ActivityTimestamps>(".Millis.HALF_SECOND", ".bar", ".progress");
export const settings = definePluginSettings({
hideActivityDetailText: {
type: OptionType.BOOLEAN,
description: "Hide the large title text next to the activity",
default: true,
},
hideActivityTimerBadges: {
type: OptionType.BOOLEAN,
description: "Hide the timer badges next to the activity",
default: true,
}
});
export default definePlugin({
name: "TimeBarAllActivities",
description: "Adds the Spotify time bar to all activities if they have start and end timestamps",
authors: [Devs.fawn, Devs.niko],
settings,
patches: [
{
find: ".Messages.USER_ACTIVITY_PLAYING",
replacement: [
// Insert Spotify time bar component
{
match: /\(0,.{0,30}activity:(\i),className:\i\.badges\}\)/g,
replace: "$&,$self.getTimeBar($1)"
},
// Hide the large title on listening activities, to make them look more like Spotify (also visible from hovering over the large icon)
{
match: /(\i).type===(\i\.\i)\.WATCHING/,
replace: "($self.settings.store.hideActivityDetailText&&$self.isActivityTimestamped($1)&&$1.type===$2.LISTENING)||$&"
}
]
},
// Hide the "badge" timers that count the time since the activity starts
{
find: ".TvIcon).otherwise",
replacement: {
match: /null!==\(\i=null===\(\i=(\i)\.timestamps\).{0,50}created_at/,
replace: "($self.settings.store.hideActivityTimerBadges&&$self.isActivityTimestamped($1))?null:$&"
}
}
],
isActivityTimestamped(activity: Activity) {
return activity.timestamps != null && activity.timestamps.start != null && activity.timestamps.end != null;
},
getTimeBar(activity: Activity) {
if (this.isActivityTimestamped(activity)) {
return <ActivityTimeBar start={activity.timestamps!.start} end={activity.timestamps!.end} />;
}
}
});

View file

@ -39,7 +39,7 @@ interface StreamData {
gainNode?: GainNode, gainNode?: GainNode,
id: string, id: string,
levelNode: AudioWorkletNode, levelNode: AudioWorkletNode,
sinkId: string, sinkId: string | "default",
stream: MediaStream, stream: MediaStream,
streamSourceNode?: MediaStreamAudioSourceNode, streamSourceNode?: MediaStreamAudioSourceNode,
videoStreamId: string, videoStreamId: string,
@ -128,6 +128,12 @@ export default definePlugin({
gain.connect(data.audioContext.destination); gain.connect(data.audioContext.destination);
} }
// @ts-expect-error
if (data.sinkId != null && data.sinkId !== data.audioContext.sinkId && "setSinkId" in AudioContext.prototype) {
// @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId
data.audioContext.setSinkId(data.sinkId);
}
data.gainNode.gain.value = data._mute data.gainNode.gain.value = data._mute
? 0 ? 0
: data._volume / 100; : data._volume / 100;

View file

@ -86,7 +86,7 @@ interface NotificationObject {
title: string; title: string;
content: string; content: string;
useBase64Icon: boolean; useBase64Icon: boolean;
icon: ArrayBuffer | string; icon: string;
sourceApp: string; sourceApp: string;
} }
@ -320,7 +320,13 @@ function shouldIgnoreForChannelType(channel: Channel) {
} }
function sendMsgNotif(titleString: string, content: string, message: Message) { function sendMsgNotif(titleString: string, content: string, message: Message) {
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`)
.then(response => response.blob())
.then(blob => new Promise<string>(resolve => {
const r = new FileReader();
r.onload = () => resolve((r.result as string).split(",")[1]);
r.readAsDataURL(blob);
})).then(result => {
const msgData: NotificationObject = { const msgData: NotificationObject = {
type: 1, type: 1,
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
@ -331,7 +337,7 @@ function sendMsgNotif(titleString: string, content: string, message: Message) {
title: titleString, title: titleString,
content: content, content: content,
useBase64Icon: true, useBase64Icon: true,
icon: new TextDecoder().decode(result), icon: result,
sourceApp: "Vencord" sourceApp: "Vencord"
}; };

View file

@ -39,7 +39,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({
}, },
Arjix: { Arjix: {
name: "ArjixWasTaken", name: "ArjixWasTaken",
id: 674710789138939916n id: 674710789138939916n,
badge: false
}, },
Cyn: { Cyn: {
name: "Cynosphere", name: "Cynosphere",
@ -267,7 +268,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
id: 841509053422632990n id: 841509053422632990n
}, },
F53: { F53: {
name: "F53", name: "Cassie (Code)",
id: 280411966126948353n id: 280411966126948353n
}, },
AutumnVN: { AutumnVN: {
@ -570,6 +571,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "niko", name: "niko",
id: 341377368075796483n, id: 341377368075796483n,
}, },
relitrix: {
name: "Relitrix",
id: 423165393901715456n,
},
RamziAH: {
name: "RamziAH",
id: 1279957227612147747n,
},
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly // iife so #__PURE__ works correctly

View file

@ -18,7 +18,7 @@
import { MessageObject } from "@api/MessageEvents"; import { MessageObject } from "@api/MessageEvents";
import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Guild, Message, User } from "discord-types/general"; import { Channel, Guild, Message, User } from "discord-types/general";
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
@ -54,12 +54,12 @@ export async function openInviteModal(code: string) {
}); });
} }
export function getCurrentChannel() { export function getCurrentChannel(): Channel | undefined {
return ChannelStore.getChannel(SelectedChannelStore.getChannelId()); return ChannelStore.getChannel(SelectedChannelStore.getChannelId());
} }
export function getCurrentGuild(): Guild | undefined { export function getCurrentGuild(): Guild | undefined {
return GuildStore.getGuild(getCurrentChannel()?.guild_id); return GuildStore.getGuild(getCurrentChannel()?.guild_id!);
} }
export function openPrivateChannel(userId: string) { export function openPrivateChannel(userId: string) {

View file

@ -91,7 +91,7 @@ export type Tooltip = ComponentType<{
/** Tooltip.Colors.BLACK */ /** Tooltip.Colors.BLACK */
color?: string; color?: string;
/** TooltipPositions.TOP */ /** TooltipPositions.TOP */
position?: string; position?: PopoutPosition;
tooltipClassName?: string; tooltipClassName?: string;
tooltipContentClassName?: string; tooltipContentClassName?: string;
@ -110,7 +110,7 @@ export type TooltipContainer = ComponentType<PropsWithChildren<{
/** Tooltip.Colors.BLACK */ /** Tooltip.Colors.BLACK */
color?: string; color?: string;
/** TooltipPositions.TOP */ /** TooltipPositions.TOP */
position?: string; position?: PopoutPosition;
spacing?: number; spacing?: number;
className?: string; className?: string;
@ -252,7 +252,7 @@ export type Select = ComponentType<PropsWithChildren<{
look?: 0 | 1; look?: 0 | 1;
className?: string; className?: string;
popoutClassName?: string; popoutClassName?: string;
popoutPosition?: "top" | "left" | "right" | "bottom" | "center" | "window_center"; popoutPosition?: PopoutPosition;
optionClassName?: string; optionClassName?: string;
autoFocus?: boolean; autoFocus?: boolean;
@ -293,7 +293,7 @@ export type SearchableSelect = ComponentType<PropsWithChildren<{
className?: string; className?: string;
popoutClassName?: string; popoutClassName?: string;
wrapperClassName?: string; wrapperClassName?: string;
popoutPosition?: "top" | "left" | "right" | "bottom" | "center" | "window_center"; popoutPosition?: PopoutPosition;
optionClassName?: string; optionClassName?: string;
autoFocus?: boolean; autoFocus?: boolean;
@ -376,6 +376,8 @@ declare enum PopoutAnimation {
FADE = "4" FADE = "4"
} }
type PopoutPosition = "top" | "bottom" | "left" | "right" | "center" | "window_center";
export type Popout = ComponentType<{ export type Popout = ComponentType<{
children( children(
thing: { thing: {
@ -387,7 +389,7 @@ export type Popout = ComponentType<{
}, },
data: { data: {
isShown: boolean; isShown: boolean;
position: string; position: PopoutPosition;
} }
): ReactNode; ): ReactNode;
shouldShow?: boolean; shouldShow?: boolean;
@ -395,7 +397,7 @@ export type Popout = ComponentType<{
closePopout(): void; closePopout(): void;
isPositioned: boolean; isPositioned: boolean;
nudge: number; nudge: number;
position: string; position: PopoutPosition;
setPopoutRef(ref: any): void; setPopoutRef(ref: any): void;
updatePosition(): void; updatePosition(): void;
}): ReactNode; }): ReactNode;
@ -404,13 +406,13 @@ export type Popout = ComponentType<{
onRequestClose?(): void; onRequestClose?(): void;
/** "center" and others */ /** "center" and others */
align?: string; align?: "left" | "right" | "center";
/** Popout.Animation */ /** Popout.Animation */
animation?: PopoutAnimation; animation?: PopoutAnimation;
autoInvert?: boolean; autoInvert?: boolean;
nudgeAlignIntoViewport?: boolean; nudgeAlignIntoViewport?: boolean;
/** "bottom" and others */ /** "bottom" and others */
position?: string; position?: PopoutPosition;
positionKey?: string; positionKey?: string;
spacing?: number; spacing?: number;
}> & { }> & {