Compare commits

..

1 commit

Author SHA1 Message Date
20cf1eb6a2
owo 2024-02-23 10:21:33 +09:00
77 changed files with 464 additions and 1163 deletions

View file

@ -62,7 +62,7 @@ function GM_fetch(url, opt) {
resp.arrayBuffer = () => blobTo("arrayBuffer", blob); resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
resp.text = () => blobTo("text", blob); resp.text = () => blobTo("text", blob);
resp.json = async () => JSON.parse(await blobTo("text", blob)); resp.json = async () => JSON.parse(await blobTo("text", blob));
resp.headers = parseHeaders(resp.responseHeaders); resp.headers = new Headers(parseHeaders(resp.responseHeaders));
resp.ok = resp.status >= 200 && resp.status < 300; resp.ok = resp.status >= 200 && resp.status < 300;
resolve(resp); resolve(resp);
}; };

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.7.2", "version": "1.6.9",
"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

@ -428,11 +428,10 @@ function runTime(token: string) {
if (searchType === "findComponent") method = "find"; if (searchType === "findComponent") method = "find";
if (searchType === "findExportedComponent") method = "findByProps"; if (searchType === "findExportedComponent") method = "findByProps";
if (searchType === "waitFor" || searchType === "waitForComponent") { if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") {
if (typeof args[0] === "string") method = "findByProps"; if (typeof args[0] === "string") method = "findByProps";
else method = "find"; else method = "find";
} }
if (searchType === "waitForStore") method = "findStore";
try { try {
let result: any; let result: any;

View file

@ -17,20 +17,22 @@
*/ */
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Menu, React } from "@webpack/common";
import type { ReactElement } from "react"; import type { ReactElement } from "react";
type ContextMenuPatchCallbackReturn = (() => void) | void;
/** /**
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void; export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
/** /**
* @param navId The navId of the context menu being patched * @param navId The navId of the context menu being patched
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
* @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates)
*/ */
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void; export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => ContextMenuPatchCallbackReturn;
const ContextMenuLogger = new Logger("ContextMenu"); const ContextMenuLogger = new Logger("ContextMenu");
@ -91,19 +93,14 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* @param id The id of the child. If an array is specified, all ids will be tried * @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children * @param children The context menu children
*/ */
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>, _itemsArray?: Array<ReactElement | null>): Array<ReactElement | null> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child);
if (found !== null) return found;
}
if ( if (
(Array.isArray(id) && id.some(id => child.props?.id === id)) (Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id || child.props?.id === id
) return children; ) return _itemsArray ?? null;
let nextChildren = child.props?.children; let nextChildren = child.props?.children;
if (nextChildren) { if (nextChildren) {
@ -112,7 +109,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren; child.props.children = nextChildren;
} }
const found = findGroupChildrenByChildId(id, nextChildren); const found = findGroupChildrenByChildId(id, nextChildren, nextChildren);
if (found !== null) return found; if (found !== null) return found;
} }
} }
@ -129,12 +126,9 @@ interface ContextMenuProps {
onClose: (callback: (...args: Array<any>) => any) => void; onClose: (callback: (...args: Array<any>) => any) => void;
} }
export function _usePatchContextMenu(props: ContextMenuProps) { const patchedMenus = new WeakSet();
props = {
...props,
children: cloneMenuChildren(props.children),
};
export function _patchContextMenu(props: ContextMenuProps) {
props.contextMenuApiArguments ??= []; props.contextMenuApiArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId); const contextMenuPatches = navPatches.get(props.navId);
@ -143,7 +137,8 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
if (contextMenuPatches) { if (contextMenuPatches) {
for (const patch of contextMenuPatches) { for (const patch of contextMenuPatches) {
try { try {
patch(props.children, ...props.contextMenuApiArguments); const callback = patch(props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) { } catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
} }
@ -152,30 +147,12 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
for (const patch of globalPatches) { for (const patch of globalPatches) {
try { try {
patch(props.navId, props.children, ...props.contextMenuApiArguments); const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments);
if (!patchedMenus.has(props)) callback?.();
} catch (err) { } catch (err) {
ContextMenuLogger.error("Global patch errored,", err); ContextMenuLogger.error("Global patch errored,", err);
} }
} }
return props; patchedMenus.add(props);
}
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren);
}
if (React.isValidElement(obj)) {
obj = React.cloneElement(obj);
if (
obj?.props?.children &&
(obj.type !== Menu.MenuControlItem || obj.type === Menu.MenuControlItem && obj.props.control != null)
) {
obj.props.children = cloneMenuChildren(obj.props.children);
}
}
return obj;
} }

View file

@ -74,7 +74,7 @@ export interface MessageExtra {
} }
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>; export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>; export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void>;
const sendListeners = new Set<SendListener>(); const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>(); const editListeners = new Set<EditListener>();
@ -84,7 +84,7 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
for (const listener of sendListeners) { for (const listener of sendListeners) {
try { try {
const result = await listener(channelId, messageObj, extra); const result = await listener(channelId, messageObj, extra);
if (result?.cancel) { if (result && result.cancel === true) {
return true; return true;
} }
} catch (e) { } catch (e) {
@ -97,15 +97,11 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec
export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) { export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) {
for (const listener of editListeners) { for (const listener of editListeners) {
try { try {
const result = await listener(channelId, messageId, messageObj); await listener(channelId, messageId, messageObj);
if (result?.cancel) {
return true;
}
} catch (e) { } catch (e) {
MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e); MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e);
} }
} }
return false;
} }
/** /**

View file

@ -223,13 +223,13 @@ export const Settings = makeProxy(settings);
export function useSettings(paths?: UseSettings<Settings>[]) { export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {}); const [, forceUpdate] = React.useReducer(() => ({}), {});
if (paths) { const onUpdate: SubscriptionCallback = paths
(forceUpdate as SubscriptionCallback)._paths = paths; ? (value, path) => paths.includes(path as UseSettings<Settings>) && forceUpdate()
} : forceUpdate;
React.useEffect(() => { React.useEffect(() => {
subscriptions.add(forceUpdate); subscriptions.add(onUpdate);
return () => void subscriptions.delete(forceUpdate); return () => void subscriptions.delete(onUpdate);
}, []); }, []);
return Settings; return Settings;
@ -253,10 +253,8 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void; export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void; export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) { export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
if (path) { if (path)
((onUpdate as SubscriptionCallback)._paths ??= []).push(path); ((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
}
subscriptions.add(onUpdate); subscriptions.add(onUpdate);
} }

View file

@ -39,7 +39,9 @@ function validateUrl(url: string) {
async function eraseAllData() { async function eraseAllData() {
const res = await fetch(new URL("/v1/", getCloudUrl()), { const res = await fetch(new URL("/v1/", getCloudUrl()), {
method: "DELETE", method: "DELETE",
headers: { Authorization: await getCloudAuth() } headers: new Headers({
Authorization: await getCloudAuth()
})
}); });
if (!res.ok) { if (!res.ok) {

View file

@ -23,7 +23,7 @@ import { debounce } from "@utils/debounce";
import { IpcEvents } from "@utils/IpcEvents"; import { IpcEvents } from "@utils/IpcEvents";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron"; import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
import { FSWatcher, mkdirSync, readFileSync, watch } from "fs"; import { mkdirSync, readFileSync, watch } from "fs";
import { open, readdir, readFile, writeFile } from "fs/promises"; import { open, readdir, readFile, writeFile } from "fs/promises";
import { join, normalize } from "path"; import { join, normalize } from "path";
@ -126,23 +126,16 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
export function initIpc(mainWindow: BrowserWindow) { export function initIpc(mainWindow: BrowserWindow) {
let quickCssWatcher: FSWatcher | undefined;
open(QUICKCSS_PATH, "a+").then(fd => { open(QUICKCSS_PATH, "a+").then(fd => {
fd.close(); fd.close();
quickCssWatcher = watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => { watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => {
mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss()); mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss());
}, 50)); }, 50));
}).catch(() => { }); });
const themesWatcher = watch(THEMES_DIR, { persistent: false }, debounce(() => { watch(THEMES_DIR, { persistent: false }, debounce(() => {
mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0); mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0);
})); }));
mainWindow.once("closed", () => {
quickCssWatcher?.close();
themesWatcher.close();
});
} }
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => { ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {

View file

@ -49,12 +49,9 @@ async function getRepo() {
async function calculateGitChanges() { async function calculateGitChanges() {
await git("fetch"); await git("fetch");
const branch = (await git("branch", "--show-current")).stdout.trim(); const branch = await git("branch", "--show-current");
const existsOnOrigin = (await git("ls-remote", "origin", branch)).stdout.length > 0; const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s");
if (!existsOnOrigin) return [];
const res = await git("log", `HEAD...origin/${branch}`, "--pretty=format:%an/%h/%s");
const commits = res.stdout.trim(); const commits = res.stdout.trim();
return commits ? commits.split("\n").map(line => { return commits ? commits.split("\n").map(line => {

View file

@ -22,15 +22,15 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "ContextMenuAPI", name: "ContextMenuAPI",
description: "API for adding/removing items to/from context menus.", description: "API for adding/removing items to/from context menus.",
authors: [Devs.Nuckyz, Devs.Ven, Devs.Kyuuhachi], authors: [Devs.Nuckyz, Devs.Ven],
required: true, required: true,
patches: [ patches: [
{ {
find: "♫ (つ。◕‿‿◕。)つ ♪", find: "♫ (つ。◕‿‿◕。)つ ♪",
replacement: { replacement: {
match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, match: /let{navId:/,
replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
} }
}, },
{ {

View file

@ -25,13 +25,10 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven], authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [ patches: [
{ {
find: ".Messages.EDIT_TEXTAREA_HELP", find: '"MessageActionCreators"',
replacement: { replacement: {
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/, match: /async editMessage\(.+?\)\{/,
replace: (match, args) => "" + replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
`async ${match}` +
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
} }
}, },
{ {

View file

@ -16,7 +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 { findGroupChildrenByChildId } from "@api/ContextMenu"; import { addContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -30,21 +30,21 @@ export default definePlugin({
authors: [Devs.Ven, Devs.Megu], authors: [Devs.Ven, Devs.Megu],
required: true, required: true,
contextMenus: { start() {
// The settings shortcuts in the user settings cog context menu // The settings shortcuts in the user settings cog context menu
// read the elements from a hardcoded map which for obvious reason // read the elements from a hardcoded map which for obvious reason
// doesn't contain our sections. This patches the actions of our // doesn't contain our sections. This patches the actions of our
// sections to manually use SettingsRouter (which only works on desktop // sections to manually use SettingsRouter (which only works on desktop
// but the context menu is usually not available on mobile anyway) // but the context menu is usually not available on mobile anyway)
"user-settings-cog"(children) { addContextMenuPatch("user-settings-cog", children => () => {
const section = findGroupChildrenByChildId("VencordSettings", children); const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
section?.forEach(c => { section?.forEach(c => {
const id = c?.props?.id; const id = c?.props?.id;
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) { if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
c!.props.action = () => SettingsRouter.open(id); c.props.action = () => SettingsRouter.open(id);
} }
}); });
} });
}, },
patches: [{ patches: [{

View file

@ -1,6 +0,0 @@
# BetterRoleContext
Adds options to copy role color and edit role when right clicking roles in the user profile
![](https://github.com/Vendicated/Vencord/assets/45497981/d1765e9e-7db2-4a3c-b110-139c59235326)

View file

@ -1,81 +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 { getCurrentGuild, getGuildRoles } from "@utils/discord";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Clipboard, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common";
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
function PencilIcon() {
return (
<svg
role="img"
width="18"
height="18"
fill="none"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z" />
</svg>
);
}
function AppearanceIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24">
<path fill="currentColor" d="M 12,0 C 5.3733333,0 0,5.3733333 0,12 c 0,6.626667 5.3733333,12 12,12 1.106667,0 2,-0.893333 2,-2 0,-0.52 -0.2,-0.986667 -0.52,-1.346667 -0.306667,-0.346666 -0.506667,-0.813333 -0.506667,-1.32 0,-1.106666 0.893334,-2 2,-2 h 2.36 C 21.013333,17.333333 24,14.346667 24,10.666667 24,4.7733333 18.626667,0 12,0 Z M 4.6666667,12 c -1.1066667,0 -2,-0.893333 -2,-2 0,-1.1066667 0.8933333,-2 2,-2 1.1066666,0 2,0.8933333 2,2 0,1.106667 -0.8933334,2 -2,2 z M 8.666667,6.6666667 c -1.106667,0 -2.0000003,-0.8933334 -2.0000003,-2 0,-1.1066667 0.8933333,-2 2.0000003,-2 1.106666,0 2,0.8933333 2,2 0,1.1066666 -0.893334,2 -2,2 z m 6.666666,0 c -1.106666,0 -2,-0.8933334 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.1066666 -0.893333,2 -2,2 z m 4,5.3333333 c -1.106666,0 -2,-0.893333 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.106667 -0.893333,2 -2,2 z" />
</svg>
);
}
export default definePlugin({
name: "BetterRoleContext",
description: "Adds options to copy role color / edit role when right clicking roles in the user profile",
authors: [Devs.Ven],
start() {
// DeveloperMode needs to be enabled for the context menu to be shown
TextAndImagesSettingsStores.DeveloperMode.updateSetting(true);
},
contextMenus: {
"dev-context"(children, { id }: { id: string; }) {
const guild = getCurrentGuild();
if (!guild) return;
const role = getGuildRoles(guild.id)[id];
if (!role) return;
if (role.colorString) {
children.push(
<Menu.MenuItem
id="vc-copy-role-color"
label="Copy Role Color"
action={() => Clipboard.copy(role.colorString!)}
icon={AppearanceIcon}
/>
);
}
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.push(
<Menu.MenuItem
id="vc-edit-role"
label="Edit Role"
action={async () => {
await GuildSettingsActions.open(guild.id, "ROLES");
GuildSettingsActions.selectRole(id);
}}
icon={PencilIcon}
/>
);
}
}
}
});

View file

@ -16,7 +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 { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { ScreenshareIcon } from "@components/Icons"; import { ScreenshareIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { openImageModal } from "@utils/discord"; import { openImageModal } from "@utils/discord";
@ -60,7 +60,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
openImageModal(previewUrl); openImageModal(previewUrl);
}; };
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => { export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => () => {
const stream = ApplicationStreamingStore.getAnyStreamForUser(userId); const stream = ApplicationStreamingStore.getAnyStreamForUser(userId);
if (!stream) return; if (!stream) return;
@ -89,8 +89,12 @@ export default definePlugin({
name: "BiggerStreamPreview", name: "BiggerStreamPreview",
description: "This plugin allows you to enlarge stream previews", description: "This plugin allows you to enlarge stream previews",
authors: [Devs.phil], authors: [Devs.phil],
contextMenus: { start: () => {
"user-context": userContextPatch, addContextMenuPatch("user-context", userContextPatch);
"stream-context": streamContextPatch addContextMenuPatch("stream-context", streamContextPatch);
},
stop: () => {
removeContextMenuPatch("user-context", userContextPatch);
removeContextMenuPatch("stream-context", streamContextPatch);
} }
}); });

View file

@ -12,7 +12,7 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType, StartAt } from "@utils/types"; import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, useStateFromStores } from "@webpack/common"; import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
@ -200,8 +200,8 @@ function captureOne(str, regex) {
return (result === null) ? null : result[1]; return (result === null) ? null : result[1];
} }
function mapReject(arr, mapFunc) { function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
return arr.map(mapFunc).filter(Boolean); return _.reject(arr.map(mapFunc), rejectFunc);
} }
function updateColorVars(color: string) { function updateColorVars(color: string) {

View file

@ -16,7 +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 { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { LinkIcon } from "@components/Icons"; import { LinkIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -29,7 +29,7 @@ interface UserContextProps {
user: User; user: User;
} }
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => { const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
if (!user) return; if (!user) return;
children.push( children.push(
@ -46,7 +46,12 @@ export default definePlugin({
name: "CopyUserURLs", name: "CopyUserURLs",
authors: [Devs.castdrian], authors: [Devs.castdrian],
description: "Adds a 'Copy User URL' option to the user context menu.", description: "Adds a 'Copy User URL' option to the user context menu.",
contextMenus: {
"user-context": UserContextMenuPatch start() {
} addContextMenuPatch("user-context", UserContextMenuPatch);
},
stop() {
removeContextMenuPatch("user-context", UserContextMenuPatch);
},
}); });

View file

@ -175,7 +175,7 @@ const settings = definePluginSettings({
}, },
startTime: { startTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "Start timestamp in milisecond (only for custom timestamp mode)", description: "Start timestamp (only for custom timestamp mode)",
onChange: onChange, onChange: onChange,
disabled: isTimestampDisabled, disabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
@ -185,7 +185,7 @@ const settings = definePluginSettings({
}, },
endTime: { endTime: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "End timestamp in milisecond (only for custom timestamp mode)", description: "End timestamp (only for custom timestamp mode)",
onChange: onChange, onChange: onChange,
disabled: isTimestampDisabled, disabled: isTimestampDisabled,
isValid: (value: number) => { isValid: (value: number) => {
@ -313,12 +313,12 @@ async function createActivity(): Promise<Activity | undefined> {
switch (settings.store.timestampMode) { switch (settings.store.timestampMode) {
case TimestampMode.NOW: case TimestampMode.NOW:
activity.timestamps = { activity.timestamps = {
start: Date.now() start: Math.floor(Date.now() / 1000)
}; };
break; break;
case TimestampMode.TIME: case TimestampMode.TIME:
activity.timestamps = { activity.timestamps = {
start: Date.now() - (new Date().getHours() * 3600 + new Date().getMinutes() * 60 + new Date().getSeconds()) * 1000 start: Math.floor(Date.now() / 1000) - (new Date().getHours() * 3600) - (new Date().getMinutes() * 60) - new Date().getSeconds()
}; };
break; break;
case TimestampMode.CUSTOM: case TimestampMode.CUSTOM:

View file

@ -72,7 +72,7 @@ export default definePlugin({
replacement: [ replacement: [
// Add Decor avatar decoration hook to avatar decoration hook // Add Decor avatar decoration hook to avatar decoration hook
{ {
match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/, match: /(?<=TryItOut:\i}\),)(?<=user:(\i).+?)/,
replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1)," replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1),"
}, },
// Use added hook // Use added hook
@ -131,10 +131,9 @@ export default definePlugin({
getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) { getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) {
// Only Decor avatar decorations have this SKU ID // Only Decor avatar decorations have this SKU ID
if (avatarDecoration?.skuId === SKU_ID) { if (avatarDecoration?.skuId === SKU_ID) {
const parts = avatarDecoration.asset.split("_"); const url = new URL(`${CDN_URL}/${avatarDecoration.asset}.png`);
// Remove a_ prefix if it's animated and animation is disabled url.searchParams.set("animate", (!!canAnimate && isAnimatedAvatarDecoration(avatarDecoration.asset)).toString());
if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift(); return url.toString();
return `${CDN_URL}/${parts.join("_")}.png`;
} else if (avatarDecoration?.skuId === RAW_SKU_ID) { } else if (avatarDecoration?.skuId === RAW_SKU_ID) {
return avatarDecoration.asset; return avatarDecoration.asset;
} }

View file

@ -16,7 +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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
@ -312,7 +312,7 @@ function isGifUrl(url: string) {
return new URL(url).pathname.endsWith(".gif"); return new URL(url).pathname.endsWith(".gif");
} }
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {}; const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {};
if (!favoriteableId) return; if (!favoriteableId) return;
@ -341,7 +341,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
findGroupChildrenByChildId("copy-link", children)?.push(menuItem); findGroupChildrenByChildId("copy-link", children)?.push(menuItem);
}; };
const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => { const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => {
const { id, name, type } = props?.target?.dataset ?? {}; const { id, name, type } = props?.target?.dataset ?? {};
if (!id) return; if (!id) return;
@ -363,8 +363,14 @@ export default definePlugin({
description: "Allows you to clone Emotes & Stickers to your own server (right click them)", description: "Allows you to clone Emotes & Stickers to your own server (right click them)",
tags: ["StickerCloner"], tags: ["StickerCloner"],
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz],
contextMenus: {
"message": messageContextMenuPatch, start() {
"expression-picker": expressionPickerPatch addContextMenuPatch("message", messageContextMenuPatch);
addContextMenuPatch("expression-picker", expressionPickerPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
removeContextMenuPatch("expression-picker", expressionPickerPatch);
} }
}); });

View file

@ -17,14 +17,14 @@
*/ */
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement, ReactNode } from "react";
@ -51,6 +51,8 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
const USE_EXTERNAL_EMOJIS = 1n << 18n;
const USE_EXTERNAL_STICKERS = 1n << 37n;
const enum EmojiIntentions { const enum EmojiIntentions {
REACTION = 0, REACTION = 0,
@ -160,28 +162,8 @@ const settings = definePluginSettings({
description: "Whether to use hyperlinks when sending fake emojis and stickers", description: "Whether to use hyperlinks when sending fake emojis and stickers",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true default: true
},
hyperLinkText: {
description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji/sticker name.",
type: OptionType.STRING,
default: "{{NAME}}"
} }
}).withPrivateSettings<{ });
disableEmbedPermissionCheck: boolean;
}>();
function hasPermission(channelId: string, permission: bigint) {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isPrivate()) return true;
return PermissionStore.can(permission, channel);
}
const hasExternalEmojiPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_EMOJIS);
const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_STICKERS);
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES);
export default definePlugin({ export default definePlugin({
name: "FakeNitro", name: "FakeNitro",
@ -369,8 +351,8 @@ export default definePlugin({
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
// Add the fake nitro emoji notice // Add the fake nitro emoji notice
match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/, match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)` replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
} }
}, },
// Allow using custom app icons // Allow using custom app icons
@ -474,7 +456,7 @@ export default definePlugin({
if (typeof firstContent === "string") { if (typeof firstContent === "string") {
content[0] = firstContent.trimStart(); content[0] = firstContent.trimStart();
content[0] || content.shift(); content[0] || content.shift();
} else if (typeof firstContent?.props?.children === "string") { } else if (firstContent?.type === "span") {
firstContent.props.children = firstContent.props.children.trimStart(); firstContent.props.children = firstContent.props.children.trimStart();
firstContent.props.children || content.shift(); firstContent.props.children || content.shift();
} }
@ -484,7 +466,7 @@ export default definePlugin({
if (typeof lastContent === "string") { if (typeof lastContent === "string") {
content[lastIndex] = lastContent.trimEnd(); content[lastIndex] = lastContent.trimEnd();
content[lastIndex] || content.pop(); content[lastIndex] || content.pop();
} else if (typeof lastContent?.props?.children === "string") { } else if (lastContent?.type === "span") {
lastContent.props.children = lastContent.props.children.trimEnd(); lastContent.props.children = lastContent.props.children.trimEnd();
lastContent.props.children || content.pop(); lastContent.props.children || content.pop();
} }
@ -585,15 +567,13 @@ export default definePlugin({
for (const [index, child] of children.entries()) children[index] = modifyChild(child); for (const [index, child] of children.entries()) children[index] = modifyChild(child);
children = this.clearEmptyArrayItems(children); children = this.clearEmptyArrayItems(children);
this.trimContent(children);
return children; return children;
}; };
try { try {
const newContent = modifyChildren(lodash.cloneDeep(content)); return modifyChildren(lodash.cloneDeep(content));
this.trimContent(newContent);
return newContent;
} catch (err) { } catch (err) {
new Logger("FakeNitro").error(err); new Logger("FakeNitro").error(err);
return content; return content;
@ -716,6 +696,22 @@ export default definePlugin({
} }
}, },
hasPermissionToUseExternalEmojis(channelId: string): boolean {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel);
},
hasPermissionToUseExternalStickers(channelId: string) {
const channel = ChannelStore.getChannel(channelId);
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true;
return PermissionStore.can(USE_EXTERNAL_STICKERS, channel);
},
getStickerLink(stickerId: string) { getStickerLink(stickerId: string) {
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`; return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`;
}, },
@ -726,7 +722,7 @@ export default definePlugin({
const { frames, width, height } = await parseURL(stickerLink); const { frames, width, height } = await parseURL(stickerLink);
const gif = GIFEncoder(); const gif = GIFEncoder();
const resolution = settings.store.stickerSize; const resolution = Settings.plugins.FakeNitro.stickerSize;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = resolution; canvas.width = resolution;
@ -787,38 +783,9 @@ export default definePlugin({
return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " "; return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
} }
function cannotEmbedNotice() { this.preSend = addPreSendListener((channelId, messageObj, extra) => {
return new Promise<boolean>(resolve => {
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>
You are trying to send/edit a message that contains a FakeNitro emoji or sticker,
however you do not have permissions to embed links in the current channel.
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
</Forms.FormText>
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>
You can disable this notice in the plugin settings.
</Forms.FormText>
</div>,
confirmText: "Send Anyway",
cancelText: "Cancel",
secondaryConfirmText: "Do not show again",
onConfirm: () => resolve(true),
onCloseCallback: () => setImmediate(() => resolve(false)),
onConfirmSecondary() {
settings.store.disableEmbedPermissionCheck = true;
resolve(true);
}
});
});
}
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
const { guildId } = this; const { guildId } = this;
let hasBypass = false;
stickerBypass: { stickerBypass: {
if (!s.enableStickerBypass) if (!s.enableStickerBypass)
break stickerBypass; break stickerBypass;
@ -831,7 +798,7 @@ export default definePlugin({
if ("pack_id" in sticker) if ("pack_id" in sticker)
break stickerBypass; break stickerBypass;
const canUseStickers = this.canUseStickers && hasExternalStickerPerms(channelId); const canUseStickers = this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId);
if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId)) if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId))
break stickerBypass; break stickerBypass;
@ -845,76 +812,47 @@ export default definePlugin({
} }
if (sticker.format_type === StickerType.APNG) { if (sticker.format_type === StickerType.APNG) {
if (!hasAttachmentPerms(channelId)) {
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>
You cannot send this message because it contains an animated FakeNitro sticker,
and you do not have permissions to attach files in the current channel. Please remove the sticker to proceed.
</Forms.FormText>
</div>
});
} else {
this.sendAnimatedSticker(link, sticker.id, channelId); this.sendAnimatedSticker(link, sticker.id, channelId);
}
return { cancel: true }; return { cancel: true };
} else { } else {
hasBypass = true;
const url = new URL(link); const url = new URL(link);
url.searchParams.set("name", sticker.name); url.searchParams.set("name", sticker.name);
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", sticker.name); messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`;
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}`;
extra.stickers!.length = 0; extra.stickers!.length = 0;
} }
} }
if (s.enableEmojiBypass) { if (s.enableEmojiBypass) {
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId); const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
for (const emoji of messageObj.validNonShortcutEmojis) { for (const emoji of messageObj.validNonShortcutEmojis) {
if (!emoji.require_colons) continue; if (!emoji.require_colons) continue;
if (emoji.available !== false && canUseEmotes) continue; if (emoji.available !== false && canUseEmotes) continue;
if (emoji.guildId === guildId && !emoji.animated) continue; if (emoji.guildId === guildId && !emoji.animated) continue;
hasBypass = true;
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(emoji.url); const url = new URL(emoji.url);
url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name); url.searchParams.set("name", emoji.name);
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name);
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
}); });
} }
} }
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
if (!await cannotEmbedNotice()) {
return { cancel: true };
}
}
return { cancel: false }; return { cancel: false };
}); });
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => { this.preEdit = addPreEditListener((channelId, __, messageObj) => {
if (!s.enableEmojiBypass) return; if (!s.enableEmojiBypass) return;
const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId);
const { guildId } = this; const { guildId } = this;
let hasBypass = false;
const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId);
messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => { messageObj.content = messageObj.content.replace(/(?<!\\)<a?:(?:\w+):(\d+)>/ig, (emojiStr, emojiId, offset, origStr) => {
const emoji = EmojiStore.getCustomEmojiById(emojiId); const emoji = EmojiStore.getCustomEmojiById(emojiId);
if (emoji == null) return emojiStr; if (emoji == null) return emojiStr;
@ -922,24 +860,12 @@ export default definePlugin({
if (emoji.available !== false && canUseEmotes) return emojiStr; if (emoji.available !== false && canUseEmotes) return emojiStr;
if (emoji.guildId === guildId && !emoji.animated) return emojiStr; if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
hasBypass = true;
const url = new URL(emoji.url); const url = new URL(emoji.url);
url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name); url.searchParams.set("name", emoji.name);
const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name); return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
}); });
if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) {
if (!await cannotEmbedNotice()) {
return { cancel: true };
}
}
return { cancel: false };
}); });
}, },

View file

@ -1,5 +0,0 @@
# FriendsSince
Shows when you became friends with someone in the user popout
![](https://github.com/Vendicated/Vencord/assets/45497981/bb258188-ab48-4c4d-9858-1e90ba41e926)

View file

@ -1,60 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { React, RelationshipStore } from "@webpack/common";
const { Heading, Text } = findByPropsLazy("Heading", "Text");
const container = findByPropsLazy("memberSinceContainer");
const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate");
const clydeMoreInfo = findByPropsLazy("clydeMoreInfo");
const locale = findByPropsLazy("getLocale");
const lastSection = findByPropsLazy("lastSection");
export default definePlugin({
name: "FriendsSince",
description: "Shows when you became friends with someone in the user popout",
authors: [Devs.Elvyra],
patches: [
{
find: ".AnalyticsSections.USER_PROFILE}",
replacement: {
match: /\i.default,\{userId:(\i.id).{0,30}}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })"
}
},
{
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
replacement: {
match: /\i.default,\{userId:(\i)}\)/,
replace: "$&,$self.friendsSince({ userId: $1 })"
}
}
],
friendsSince: ErrorBoundary.wrap(({ userId }: { userId: string; }) => {
const friendsSince = RelationshipStore.getSince(userId);
if (!friendsSince) return null;
return (
<div className={lastSection.section}>
<Heading variant="eyebrow" className={clydeMoreInfo.title}>
Friends Since
</Heading>
<div className={container.memberSinceContainer}>
<Text variant="text-sm/normal" className={clydeMoreInfo.body}>
{getCreatedAtDate(friendsSince, locale.getLocale())}
</Text>
</div>
</div>
);
}, { noop: true })
});

View file

@ -5,14 +5,12 @@
*/ */
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common"; import { StatusSettingsStores, Tooltip } from "webpack/common";
const enum ActivitiesTypes { const enum ActivitiesTypes {
Game, Game,
@ -71,113 +69,7 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old); StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
} }
function ImportCustomRPCComponent() { const settings = definePluginSettings({}).withPrivateSettings<{
return (
<Flex flexDirection="column">
<Forms.FormText type={Forms.FormText.Types.DESCRIPTION}>Import the application id of the CustomRPC plugin to the allowed list</Forms.FormText>
<div>
<Button
onClick={() => {
const id = Settings.plugins.CustomRPC?.appID as string | undefined;
if (!id) {
return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE);
}
const isAlreadyAdded = allowedIdsPushID?.(id);
if (isAlreadyAdded) {
showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE);
}
}}
>
Import CustomRPC ID
</Button>
</div>
</Flex>
);
}
let allowedIdsPushID: ((id: string) => boolean) | null = null;
function AllowedIdsComponent(props: { setValue: (value: string) => void; }) {
const [allowedIds, setAllowedIds] = useState<string>(settings.store.allowedIds ?? "");
allowedIdsPushID = (id: string) => {
const currentIds = new Set(allowedIds.split(",").map(id => id.trim()).filter(Boolean));
const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false);
const ids = Array.from(currentIds).join(", ");
setAllowedIds(ids);
props.setValue(ids);
return isAlreadyAdded;
};
useEffect(() => () => {
allowedIdsPushID = null;
}, []);
function handleChange(newValue: string) {
setAllowedIds(newValue);
props.setValue(newValue);
}
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">Allowed List</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Comma separated list of activity IDs to allow (Useful for allowing RPC activities and CustomRPC)</Forms.FormText>
<TextInput
type="text"
value={allowedIds}
onChange={handleChange}
placeholder="235834946571337729, 343383572805058560"
/>
</Forms.FormSection>
);
}
const settings = definePluginSettings({
importCustomRPC: {
type: OptionType.COMPONENT,
description: "",
component: () => <ImportCustomRPCComponent />
},
allowedIds: {
type: OptionType.COMPONENT,
description: "",
default: "",
onChange(newValue: string) {
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
settings.store.allowedIds = Array.from(ids).join(", ");
},
component: props => <AllowedIdsComponent setValue={props.setValue} />
},
ignorePlaying: {
type: OptionType.BOOLEAN,
description: "Ignore all playing activities (These are usually game and RPC activities)",
default: false
},
ignoreStreaming: {
type: OptionType.BOOLEAN,
description: "Ignore all streaming activities",
default: false
},
ignoreListening: {
type: OptionType.BOOLEAN,
description: "Ignore all listening activities (These are usually spotify activities)",
default: false
},
ignoreWatching: {
type: OptionType.BOOLEAN,
description: "Ignore all watching activities",
default: false
},
ignoreCompeting: {
type: OptionType.BOOLEAN,
description: "Ignore all competing activities (These are normally special game activities)",
default: false
}
}).withPrivateSettings<{
ignoredActivities: IgnoredActivity[]; ignoredActivities: IgnoredActivity[];
}>(); }>();
@ -185,26 +77,10 @@ function getIgnoredActivities() {
return settings.store.ignoredActivities ??= []; return settings.store.ignoredActivities ??= [];
} }
function isActivityTypeIgnored(type: number, id?: string) {
if (id && settings.store.allowedIds.includes(id)) {
return false;
}
switch (type) {
case 0: return settings.store.ignorePlaying;
case 1: return settings.store.ignoreStreaming;
case 2: return settings.store.ignoreListening;
case 3: return settings.store.ignoreWatching;
case 5: return settings.store.ignoreCompeting;
}
return false;
}
export default definePlugin({ export default definePlugin({
name: "IgnoreActivities", name: "IgnoreActivities",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
settings, settings,
@ -265,17 +141,13 @@ export default definePlugin({
}, },
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
if (isActivityTypeIgnored(props.type, props.application_id)) return false; if (props.type === 0 || props.type === 3) {
if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
if (props.application_id != null) { else {
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || settings.store.allowedIds.includes(props.application_id);
} else {
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
if (exePath) { if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
return !getIgnoredActivities().some(activity => activity.id === exePath);
} }
} }
return true; return true;
}, },

View file

@ -16,14 +16,14 @@
* 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 { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { debounce } from "@utils/debounce"; import { debounce } from "@utils/debounce";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Menu, React, ReactDOM } from "@webpack/common"; import { ContextMenuApi, Menu, React, ReactDOM } from "@webpack/common";
import type { Root } from "react-dom/client"; import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { Magnifier, MagnifierProps } from "./components/Magnifier";
@ -80,25 +80,25 @@ export const settings = definePluginSettings({
}); });
const imageContextMenuPatch: NavContextMenuPatchCallback = children => { const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
children.push( children.push(
<Menu.MenuGroup id="image-zoom"> <Menu.MenuGroup id="image-zoom">
<Menu.MenuCheckboxItem <Menu.MenuCheckboxItem
id="vc-square" id="vc-square"
label="Square Lens" label="Square Lens"
checked={square} checked={settings.store.square}
action={() => { action={() => {
settings.store.square = !square; settings.store.square = !settings.store.square;
ContextMenuApi.closeContextMenu();
}} }}
/> />
<Menu.MenuCheckboxItem <Menu.MenuCheckboxItem
id="vc-nearest-neighbour" id="vc-nearest-neighbour"
label="Nearest Neighbour" label="Nearest Neighbour"
checked={nearestNeighbour} checked={settings.store.nearestNeighbour}
action={() => { action={() => {
settings.store.nearestNeighbour = !nearestNeighbour; settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
ContextMenuApi.closeContextMenu();
}} }}
/> />
<Menu.MenuControlItem <Menu.MenuControlItem
@ -196,9 +196,6 @@ export default definePlugin({
], ],
settings, settings,
contextMenus: {
"image-context": imageContextMenuPatch
},
// to stop from rendering twice /shrug // to stop from rendering twice /shrug
currentMagnifierElement: null as React.FunctionComponentElement<MagnifierProps & JSX.IntrinsicAttributes> | null, currentMagnifierElement: null as React.FunctionComponentElement<MagnifierProps & JSX.IntrinsicAttributes> | null,
@ -248,6 +245,7 @@ export default definePlugin({
start() { start() {
enableStyle(styles); enableStyle(styles);
addContextMenuPatch("image-context", imageContextMenuPatch);
this.element = document.createElement("div"); this.element = document.createElement("div");
this.element.classList.add("MagnifierContainer"); this.element.classList.add("MagnifierContainer");
document.body.appendChild(this.element); document.body.appendChild(this.element);
@ -258,5 +256,6 @@ export default definePlugin({
// so componenetWillUnMount gets called if Magnifier component is still alive // so componenetWillUnMount gets called if Magnifier component is still alive
this.root && this.root.unmount(); this.root && this.root.unmount();
this.element?.remove(); this.element?.remove();
removeContextMenuPatch("image-context", imageContextMenuPatch);
} }
}); });

View file

@ -9,9 +9,6 @@
box-shadow: inset 0 0 10px 2px grey; box-shadow: inset 0 0 10px 2px grey;
filter: drop-shadow(0 0 2px grey); filter: drop-shadow(0 0 2px grey);
pointer-events: none; pointer-events: none;
/* negate the border offsetting the lens */
margin: -2px;
} }
.vc-imgzoom-square { .vc-imgzoom-square {

View file

@ -17,7 +17,6 @@
*/ */
import { registerCommand, unregisterCommand } from "@api/Commands"; import { registerCommand, unregisterCommand } from "@api/Commands";
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Patch, Plugin, StartAt } from "@utils/types"; import { Patch, Plugin, StartAt } from "@utils/types";
@ -120,7 +119,7 @@ export function startDependenciesRecursive(p: Plugin) {
} }
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p; const { name, commands, flux } = p;
if (p.start) { if (p.start) {
logger.info("Starting plugin", name); logger.info("Starting plugin", name);
@ -155,17 +154,11 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
} }
} }
if (contextMenus) {
for (const navId in contextMenus) {
addContextMenuPatch(navId, contextMenus[navId]);
}
}
return true; return true;
}, p => `startPlugin ${p.name}`); }, p => `startPlugin ${p.name}`);
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux, contextMenus } = p; const { name, commands, flux } = p;
if (p.stop) { if (p.stop) {
logger.info("Stopping plugin", name); logger.info("Stopping plugin", name);
if (!p.started) { if (!p.started) {
@ -199,11 +192,5 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
} }
} }
if (contextMenus) {
for (const navId in contextMenus) {
removeContextMenuPatch(navId, contextMenus[navId]);
}
}
return true; return true;
}, p => `stopPlugin ${p.name}`); }, p => `stopPlugin ${p.name}`);

View file

@ -170,11 +170,6 @@ const settings = definePluginSettings({
} }
], ],
}, },
showLastFmLogo: {
description: "show the Last.fm logo by the album cover",
type: OptionType.BOOLEAN,
default: true,
}
}); });
export default definePlugin({ export default definePlugin({
@ -281,10 +276,8 @@ export default definePlugin({
{ {
large_image: await getApplicationAsset(largeImage), large_image: await getApplicationAsset(largeImage),
large_text: trackData.album || undefined, large_text: trackData.album || undefined,
...(settings.store.showLastFmLogo && {
small_image: await getApplicationAsset("lastfm-small"), small_image: await getApplicationAsset("lastfm-small"),
small_text: "Last.fm" small_text: "Last.fm",
}),
} : { } : {
large_image: await getApplicationAsset("lastfm-large"), large_image: await getApplicationAsset("lastfm-large"),
large_text: trackData.album || undefined, large_text: trackData.album || undefined,

View file

@ -1,66 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { getCurrentChannel } from "@utils/discord";
import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from ".";
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id;
const totalCount = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
let onlineCount = useStateFromStores(
[OnlineMemberCountStore],
() => OnlineMemberCountStore.getCount(guildId)
);
const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, currentChannel.id)
);
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {
onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0);
}
useEffect(() => {
OnlineMemberCountStore.ensureCount(guildId);
}, [guildId]);
if (totalCount == null)
return null;
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
return (
<div className={cl("widget", { tooltip: isTooltip, "member-list": !isTooltip })}>
<Tooltip text={`${formattedOnlineCount} online in this channel`} position="bottom">
{props => (
<div {...props}>
<span className={cl("online-dot")} />
<span className={cl("online")}>{formattedOnlineCount}</span>
</div>
)}
</Tooltip>
<Tooltip text={`${numberFormat(totalCount)} total server members`} position="bottom">
{props => (
<div {...props}>
<span className={cl("total-dot")} />
<span className={cl("total")}>{numberFormat(totalCount)}</span>
</div>
)}
</Tooltip>
</div>
);
}

View file

@ -1,52 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { proxyLazy } from "@utils/lazy";
import { sleep } from "@utils/misc";
import { Queue } from "@utils/Queue";
import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common";
export const OnlineMemberCountStore = proxyLazy(() => {
const preloadQueue = new Queue();
const onlineMemberMap = new Map<string, number>();
class OnlineMemberCountStore extends Flux.Store {
getCount(guildId: string) {
return onlineMemberMap.get(guildId);
}
async _ensureCount(guildId: string) {
if (onlineMemberMap.has(guildId)) return;
await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id);
}
ensureCount(guildId: string) {
if (onlineMemberMap.has(guildId)) return;
preloadQueue.push(() =>
this._ensureCount(guildId)
.then(
() => sleep(200),
() => sleep(200)
)
);
}
}
return new OnlineMemberCountStore(FluxDispatcher, {
GUILD_MEMBER_LIST_UPDATE({ guildId, groups }: { guildId: string, groups: { count: number; id: string; }[]; }) {
onlineMemberMap.set(
guildId,
groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0)
);
},
ONLINE_GUILD_MEMBER_COUNT_UPDATE({ guildId, count }) {
onlineMemberMap.set(guildId, count);
}
});
});

View file

@ -16,66 +16,101 @@
* 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 "./style.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import { getCurrentChannel } from "@utils/discord";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack"; import { findStoreLazy } from "@webpack";
import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
import { FluxStore } from "@webpack/types"; import { FluxStore } from "@webpack/types";
import { MemberCount } from "./MemberCount"; const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
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; }[]; };
}; };
const settings = definePluginSettings({
toolTip: {
type: OptionType.BOOLEAN,
description: "If the member count should be displayed on the server tooltip",
default: true,
restartNeeded: true
},
memberList: {
type: OptionType.BOOLEAN,
description: "If the member count should be displayed on the member list",
default: true,
restartNeeded: true
}
});
const sharedIntlNumberFormat = new Intl.NumberFormat(); const sharedIntlNumberFormat = new Intl.NumberFormat();
export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value); const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
export const cl = classNameFactory("vc-membercount-");
function MemberCount() {
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, channelId)
);
const total = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
if (total == null)
return null;
const online =
(groups.length === 1 && groups[0].id === "unknown")
? 0
: groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
return (
<Flex id="vc-membercount" style={{
marginTop: "1em",
paddingInline: "1em",
justifyContent: "center",
alignContent: "center",
gap: 0
}}>
<Tooltip text={`${numberFormat(online)} online in this channel`} position="bottom">
{props => (
<div {...props}>
<span
style={{
backgroundColor: "var(--green-360)",
width: "12px",
height: "12px",
borderRadius: "50%",
display: "inline-block",
marginRight: "0.5em"
}}
/>
<span style={{ color: "var(--green-360)" }}>{numberFormat(online)}</span>
</div>
)}
</Tooltip>
<Tooltip text={`${numberFormat(total)} total server members`} position="bottom">
{props => (
<div {...props}>
<span
style={{
width: "6px",
height: "6px",
borderRadius: "50%",
border: "3px solid var(--primary-400)",
display: "inline-block",
marginRight: "0.5em",
marginLeft: "1em"
}}
/>
<span style={{ color: "var(--primary-400)" }}>{numberFormat(total)}</span>
</div>
)}
</Tooltip>
</Flex>
);
}
export default definePlugin({ export default definePlugin({
name: "MemberCount", name: "MemberCount",
description: "Shows the amount of online & total members in the server member list and tooltip", description: "Shows the amount of online & total members in the server member list",
authors: [Devs.Ven, Devs.Commandtechno], authors: [Devs.Ven, Devs.Commandtechno],
settings,
patches: [ patches: [{
{
find: "{isSidebarVisible:", find: "{isSidebarVisible:",
replacement: { replacement: {
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2" replace: ":[$1?.startsWith('members')?$self.render():null,$2"
},
predicate: () => settings.store.memberList
},
{
find: ".invitesDisabledTooltip",
replacement: {
match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/,
replace: ",$self.renderTooltip(arguments[0].guild)]"
},
predicate: () => settings.store.toolTip
} }
], }],
render: ErrorBoundary.wrap(MemberCount, { noop: true }),
renderTooltip: ErrorBoundary.wrap(guild => <MemberCount isTooltip tooltipGuildId={guild.id} />, { noop: true }) render: ErrorBoundary.wrap(MemberCount, { noop: true })
}); });

View file

@ -1,44 +0,0 @@
.vc-membercount-widget {
display: flex;
align-content: center;
--color-online: var(--green-360);
--color-total: var(--primary-400);
}
.vc-membercount-tooltip {
margin-top: 0.25em;
margin-left: 2px;
}
.vc-membercount-member-list {
justify-content: center;
margin-top: 1em;
padding-inline: 1em;
}
.vc-membercount-online {
color: var(--color-online);
}
.vc-membercount-total {
color: var(--color-total);
}
.vc-membercount-online-dot {
background-color: var(--color-online);
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 0.5em;
}
.vc-membercount-total-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
border: 3px solid var(--color-total);
margin: 0 0.5em 0 1em;
}

View file

@ -18,7 +18,7 @@
import "./messageLogger.css"; import "./messageLogger.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
@ -45,7 +45,7 @@ function addDeleteStyle() {
const REMOVE_HISTORY_ID = "ml-remove-history"; const REMOVE_HISTORY_ID = "ml-remove-history";
const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style"; const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style";
const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => { const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => () => {
const { message } = props; const { message } = props;
const { deleted, editHistory, id, channel_id } = message; const { deleted, editHistory, id, channel_id } = message;
@ -94,12 +94,13 @@ export default definePlugin({
description: "Temporarily logs deleted and edited messages.", description: "Temporarily logs deleted and edited messages.",
authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN], authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN],
contextMenus: {
"message": patchMessageContextMenu
},
start() { start() {
addDeleteStyle(); addDeleteStyle();
addContextMenuPatch("message", patchMessageContextMenu);
},
stop() {
removeContextMenuPatch("message", patchMessageContextMenu);
}, },
renderEdit(edit: { timestamp: any, content: string; }) { renderEdit(edit: { timestamp: any, content: string; }) {

View file

@ -16,18 +16,16 @@
* 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 { definePluginSettings,migratePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels");
const { isOptInEnabledForGuild } = findByPropsLazy("isOptInEnabledForGuild");
const settings = definePluginSettings({ const settings = definePluginSettings({
guild: { guild: {
description: "Mute Guild automatically", description: "Mute Guild",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true default: true
}, },
@ -40,20 +38,13 @@ const settings = definePluginSettings({
description: "Suppress All Role @mentions", description: "Suppress All Role @mentions",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true default: true
},
showAllChannels: {
description: "Show all channels automatically",
type: OptionType.BOOLEAN,
default: true
} }
}); });
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
export default definePlugin({ export default definePlugin({
name: "NewGuildSettings", name: "MuteNewGuild",
description: "Automatically mute new servers and change various other settings upon joining", description: "Mutes newly joined guilds",
tags: ["MuteNewGuild", "mute", "server"], authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi],
patches: [ patches: [
{ {
find: ",acceptInvite(", find: ",acceptInvite(",
@ -79,9 +70,7 @@ export default definePlugin({
muted: settings.store.guild, muted: settings.store.guild,
suppress_everyone: settings.store.everyone, suppress_everyone: settings.store.everyone,
suppress_roles: settings.store.role suppress_roles: settings.store.role
});
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
toggleShowAllChannels(guildId);
} }
);
} }
}); });

View file

@ -47,8 +47,8 @@ export default definePlugin({
{ {
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
replacement: { replacement: {
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
} }
}, },
{ {

View file

@ -27,8 +27,8 @@ export default definePlugin({
{ {
find: "_ensureAudio(){", find: "_ensureAudio(){",
replacement: { replacement: {
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/, match: /onloadeddata=\(\)=>\{.\.volume=/,
replace: "$self.settings.store.notificationVolume/100*" replace: "$&$self.settings.store.notificationVolume/100*"
}, },
}, },
], ],

View file

@ -19,7 +19,7 @@
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
import { getGuildRoles, getUniqueUsername } from "@utils/discord"; import { getUniqueUsername } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; import { ContextMenuApi, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common";
import type { Guild } from "discord-types/general"; import type { Guild } from "discord-types/general";
@ -78,8 +78,6 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
const [selectedItemIndex, selectItem] = useState(0); const [selectedItemIndex, selectItem] = useState(0);
const selectedItem = permissions[selectedItemIndex]; const selectedItem = permissions[selectedItemIndex];
const roles = getGuildRoles(guild.id);
return ( return (
<ModalRoot <ModalRoot
{...modalProps} {...modalProps}
@ -102,7 +100,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<div className={cl("perms-list")}> <div className={cl("perms-list")}>
{permissions.map((permission, index) => { {permissions.map((permission, index) => {
const user = UserStore.getUser(permission.id ?? ""); const user = UserStore.getUser(permission.id ?? "");
const role = roles[permission.id ?? ""]; const role = guild.roles[permission.id ?? ""];
return ( return (
<button <button
@ -203,7 +201,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str
id="vc-pw-view-as-role" id="vc-pw-view-as-role"
label="View As Role" label="View As Role"
action={() => { action={() => {
const role = getGuildRoles(guild.id)[roleId]; const role = guild.roles[roleId];
if (!role) return; if (!role) return;
onClose(); onClose();

View file

@ -18,10 +18,9 @@
import "./styles.css"; import "./styles.css";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getGuildRoles } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common";
import type { Guild, GuildMember } from "discord-types/general"; import type { Guild, GuildMember } from "discord-types/general";
@ -108,7 +107,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
} }
default: { default: {
permissions = Object.values(getGuildRoles(guild.id)).map(role => ({ permissions = Object.values(guild.roles).map(role => ({
type: PermissionType.Role, type: PermissionType.Role,
...role ...role
})); }));
@ -126,10 +125,10 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
} }
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback { function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
return (children, props) => { return (children, props) => () => {
if (!props) return; if (!props) return;
if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild))) if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
return; return children;
const group = findGroupChildrenByChildId(childId, children); const group = findGroupChildrenByChildId(childId, children);
@ -174,10 +173,19 @@ export default definePlugin({
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />, UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
contextMenus: { userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User),
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User), channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel), guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild),
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild) start() {
} addContextMenuPatch("user-context", this.userContextMenuPatch);
addContextMenuPatch("channel-context", this.channelContextMenuPatch);
addContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
stop() {
removeContextMenuPatch("user-context", this.userContextMenuPatch);
removeContextMenuPatch("channel-context", this.channelContextMenuPatch);
removeContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch);
},
}); });

View file

@ -17,9 +17,8 @@
*/ */
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { getGuildRoles } from "@utils/discord";
import { wordsToTitle } from "@utils/text"; import { wordsToTitle } from "@utils/text";
import { i18n, Parser } from "@webpack/common"; import { GuildStore, i18n, Parser } from "@webpack/common";
import { Guild, GuildMember, Role } from "discord-types/general"; import { Guild, GuildMember, Role } from "discord-types/general";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@ -68,9 +67,7 @@ export function getPermissionDescription(permission: string): ReactNode {
return ""; return "";
} }
export function getSortedRoles({ id }: Guild, member: GuildMember) { export function getSortedRoles({ roles, id }: Guild, member: GuildMember) {
const roles = getGuildRoles(id);
return [...member.roles, id] return [...member.roles, id]
.map(id => roles[id]) .map(id => roles[id])
.sort((a, b) => b.position - a.position); .sort((a, b) => b.position - a.position);
@ -88,13 +85,13 @@ export function sortUserRoles(roles: Role[]) {
} }
export function sortPermissionOverwrites<T extends { id: string; type: number; }>(overwrites: T[], guildId: string) { export function sortPermissionOverwrites<T extends { id: string; type: number; }>(overwrites: T[], guildId: string) {
const roles = getGuildRoles(guildId); const guild = GuildStore.getGuild(guildId);
return overwrites.sort((a, b) => { return overwrites.sort((a, b) => {
if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0; if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0;
const roleA = roles[a.id]; const roleA = guild.roles[a.id];
const roleB = roles[b.id]; const roleB = guild.roles[b.id];
return roleB.position - roleA.position; return roleB.position - roleA.position;
}); });

View file

@ -16,7 +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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
import { isPinned, movePin, PinOrder, settings, snapshotArray, togglePin } from "./settings"; import { isPinned, movePin, PinOrder, settings, snapshotArray, togglePin } from "./settings";
@ -50,13 +50,13 @@ function PinMenuItem(channelId: string) {
); );
} }
const GroupDMContext: NavContextMenuPatchCallback = (children, props) => { const GroupDMContext: NavContextMenuPatchCallback = (children, props) => () => {
const container = findGroupChildrenByChildId("leave-channel", children); const container = findGroupChildrenByChildId("leave-channel", children);
if (container) if (container)
container.unshift(PinMenuItem(props.channel.id)); container.unshift(PinMenuItem(props.channel.id));
}; };
const UserContext: NavContextMenuPatchCallback = (children, props) => { const UserContext: NavContextMenuPatchCallback = (children, props) => () => {
const container = findGroupChildrenByChildId("close-dm", children); const container = findGroupChildrenByChildId("close-dm", children);
if (container) { if (container) {
const idx = container.findIndex(c => c?.props?.id === "close-dm"); const idx = container.findIndex(c => c?.props?.id === "close-dm");
@ -64,7 +64,12 @@ const UserContext: NavContextMenuPatchCallback = (children, props) => {
} }
}; };
export const contextMenus = { export function addContextMenus() {
"gdm-context": GroupDMContext, addContextMenuPatch("gdm-context", GroupDMContext);
"user-context": UserContext addContextMenuPatch("user-context", UserContext);
}; }
export function removeContextMenus() {
removeContextMenuPatch("gdm-context", GroupDMContext);
removeContextMenuPatch("user-context", UserContext);
}

View file

@ -20,7 +20,7 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
import { contextMenus } from "./contextMenus"; import { addContextMenus, removeContextMenus } from "./contextMenus";
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings"; import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
export default definePlugin({ export default definePlugin({
@ -29,7 +29,9 @@ export default definePlugin({
authors: [Devs.Ven, Devs.Strencher], authors: [Devs.Ven, Devs.Strencher],
settings, settings,
contextMenus,
start: addContextMenus,
stop: removeContextMenus,
usePinCount(channelIds: string[]) { usePinCount(channelIds: string[]) {
const pinnedDms = usePinnedDms(); const pinnedDms = usePinnedDms();

View file

@ -1,5 +0,0 @@
# ResurrectHome
Brings back the phased out [Server Home](https://support.discord.com/hc/en-us/articles/6156116949911-Server-Home-Beta) feature!
![](https://github.com/Vendicated/Vencord/assets/61953774/98d5d667-bbb9-48b8-872d-c9b3980f6506)

View file

@ -1,119 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Menu } from "@webpack/common";
const settings = definePluginSettings({
forceServerHome: {
type: OptionType.BOOLEAN,
description: "Force the Server Guide to be the Server Home tab when it is enabled.",
default: false
}
});
function useForceServerHome() {
const { forceServerHome } = settings.use(["forceServerHome"]);
return forceServerHome;
}
export default definePlugin({
name: "ResurrectHome",
description: "Re-enables the Server Home tab when there isn't a Server Guide. Also has an option to force the Server Home over the Server Guide, which is accessible through right-clicking the Server Guide.",
authors: [Devs.Dolfies, Devs.Nuckyz],
settings,
patches: [
// Force home deprecation override
{
find: "GuildFeatures.GUILD_HOME_DEPRECATION_OVERRIDE",
all: true,
replacement: [
{
match: /\i\.hasFeature\(\i\.GuildFeatures\.GUILD_HOME_DEPRECATION_OVERRIDE\)/g,
replace: "true"
}
],
},
// Disable feedback prompts
{
find: "GuildHomeFeedbackExperiment.definition.id",
replacement: [
{
match: /return{showFeedback:\i,setOnDismissedFeedback:(\i)}/,
replace: "return{showFeedback:false,setOnDismissedFeedback:$1}"
}
]
},
// This feature was never finished, so the patch is disabled
// Enable guild feed render mode selector
// {
// find: "2022-01_home_feed_toggle",
// replacement: [
// {
// match: /showSelector:!1/,
// replace: "showSelector:true"
// }
// ]
// },
// Fix focusMessage clearing previously cached messages and causing a loop when fetching messages around home messages
{
find: '"MessageActionCreators"',
replacement: {
match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
replace: "before:$1,"
}
},
// Force Server Home instead of Server Guide
{
find: "61eef9_2",
replacement: {
match: /(?<=getMutableGuildChannelsForGuild\(\i\)\);)(?=if\(null==\i\|\|)/,
replace: "if($self.useForceServerHome())return false;"
}
}
],
useForceServerHome,
contextMenus: {
"guild-context"(children, props) {
const forceServerHome = useForceServerHome();
if (!props?.guild) return;
const group = findGroupChildrenByChildId("hide-muted-channels", children);
group?.unshift(
<Menu.MenuCheckboxItem
key="force-server-home"
id="force-server-home"
label="Force Server Home"
checked={forceServerHome}
action={() => settings.store.forceServerHome = !forceServerHome}
/>
);
}
}
});

View file

@ -16,7 +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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { OpenExternalIcon } from "@components/Icons"; import { OpenExternalIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -84,7 +84,7 @@ function makeSearchItem(src: string) {
); );
} }
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
if (props?.reverseImageSearchType !== "img") return; if (props?.reverseImageSearchType !== "img") return;
const src = props.itemHref ?? props.itemSrc; const src = props.itemHref ?? props.itemSrc;
@ -93,7 +93,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
group?.push(makeSearchItem(src)); group?.push(makeSearchItem(src));
}; };
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
if (!props?.src) return; if (!props?.src) return;
const group = findGroupChildrenByChildId("copy-native-link", children) ?? children; const group = findGroupChildrenByChildId("copy-native-link", children) ?? children;
@ -115,8 +115,14 @@ export default definePlugin({
} }
} }
], ],
contextMenus: {
"message": messageContextMenuPatch, start() {
"image-context": imageContextMenuPatch addContextMenuPatch("message", messageContextMenuPatch);
addContextMenuPatch("image-context", imageContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
removeContextMenuPatch("image-context", imageContextMenuPatch);
} }
}); });

View file

@ -59,7 +59,7 @@ export function authorize(callback?: any) {
const url = new URL(response.location); const url = new URL(response.location);
url.searchParams.append("clientMod", "vencord"); url.searchParams.append("clientMod", "vencord");
const res = await fetch(url, { const res = await fetch(url, {
headers: { Accept: "application/json" } headers: new Headers({ Accept: "application/json" })
}); });
if (!res.ok) { if (!res.ok) {

View file

@ -16,8 +16,8 @@
* 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 { useAwaiter, useForceUpdater } from "@utils/react"; import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { find, findByPropsLazy } from "@webpack";
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";
@ -31,8 +31,7 @@ import ReviewComponent from "./ReviewComponent";
const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms"); const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
const { ChatInputTypes } = findByPropsLazy("ChatInputTypes"); const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
const InputComponent = findComponentByCodeLazy("default.CHANNEL_TEXT_AREA"); const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default);
const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer");
interface UserProps { interface UserProps {
discordId: string; discordId: string;
@ -126,7 +125,19 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
const inputType = ChatInputTypes.FORM; const inputType = ChatInputTypes.FORM;
inputType.disableAutoFocus = true; inputType.disableAutoFocus = true;
const channel = createChannelRecordFromServer({ id: "0", type: 1 }); const channel = {
flags_: 256,
guild_id_: null,
id: "0",
getGuildId: () => null,
isPrivate: () => true,
isActiveThread: () => false,
isArchivedLockedThread: () => false,
isDM: () => true,
roles: { "0": { permissions: 0n } },
getRecipientId: () => "0",
hasFlag: () => false,
};
return ( return (
<> <>

View file

@ -18,7 +18,7 @@
import "./style.css"; import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import ExpandableHeader from "@components/ExpandableHeader"; import ExpandableHeader from "@components/ExpandableHeader";
import { OpenExternalIcon } from "@components/Icons"; import { OpenExternalIcon } from "@components/Icons";
@ -36,7 +36,7 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
import { showToast } from "./utils"; import { showToast } from "./utils";
const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => { const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => {
children.push( children.push(
<Menu.MenuItem <Menu.MenuItem
label="View Reviews" label="View Reviews"
@ -53,9 +53,6 @@ export default definePlugin({
authors: [Devs.mantikafasi, Devs.Ven], authors: [Devs.mantikafasi, Devs.Ven],
settings, settings,
contextMenus: {
"guild-header-popout": guildPopoutPatch
},
patches: [ patches: [
{ {
@ -72,6 +69,8 @@ export default definePlugin({
}, },
async start() { async start() {
addContextMenuPatch("guild-header-popout", guildPopoutPatch);
const s = settings.store; const s = settings.store;
const { lastReviewId, notifyReviews } = s; const { lastReviewId, notifyReviews } = s;
@ -128,6 +127,10 @@ export default definePlugin({
}, 4000); }, 4000);
}, },
stop() {
removeContextMenuPatch("guild-header-popout", guildPopoutPatch);
},
getReviewsComponent: ErrorBoundary.wrap((user: User) => { getReviewsComponent: ErrorBoundary.wrap((user: User) => {
const [reviewCount, setReviewCount] = useState<number>(); const [reviewCount, setReviewCount] = useState<number>();

View file

@ -118,10 +118,10 @@ export async function addReview(review: any): Promise<Response | null> {
export async function deleteReview(id: number): Promise<Response | null> { export async function deleteReview(id: number): Promise<Response | null> {
return await rdbRequest(`/users/${id}/reviews`, { return await rdbRequest(`/users/${id}/reviews`, {
method: "DELETE", method: "DELETE",
headers: { headers: new Headers({
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, }),
body: JSON.stringify({ body: JSON.stringify({
reviewid: id reviewid: id
}) })
@ -135,10 +135,10 @@ export async function deleteReview(id: number): Promise<Response | null> {
export async function reportReview(id: number) { export async function reportReview(id: number) {
const res = await rdbRequest("/reports", { const res = await rdbRequest("/reports", {
method: "PUT", method: "PUT",
headers: { headers: new Headers({
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, }),
body: JSON.stringify({ body: JSON.stringify({
reviewid: id, reviewid: id,
}) })
@ -150,10 +150,10 @@ export async function reportReview(id: number) {
async function patchBlock(action: "block" | "unblock", userId: string) { async function patchBlock(action: "block" | "unblock", userId: string) {
const res = await rdbRequest("/blocks", { const res = await rdbRequest("/blocks", {
method: "PATCH", method: "PATCH",
headers: { headers: new Headers({
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, }),
body: JSON.stringify({ body: JSON.stringify({
action: action, action: action,
discordId: userId discordId: userId
@ -180,9 +180,9 @@ export const unblockUser = (userId: string) => patchBlock("unblock", userId);
export async function fetchBlocks(): Promise<ReviewDBUser[]> { export async function fetchBlocks(): Promise<ReviewDBUser[]> {
const res = await rdbRequest("/blocks", { const res = await rdbRequest("/blocks", {
method: "GET", method: "GET",
headers: { headers: new Headers({
Accept: "application/json", Accept: "application/json",
} })
}); });
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`); if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);

View file

@ -17,11 +17,9 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getGuildRoles } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, GuildMemberStore } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";
const settings = definePluginSettings({ const settings = definePluginSettings({
chatMentions: { chatMentions: {
@ -114,8 +112,9 @@ export default definePlugin({
return colorString && parseInt(colorString.slice(1), 16); return colorString && parseInt(colorString.slice(1), 16);
}, },
roleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { roleGroupColor({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) {
const role = getGuildRoles(guildId)[id]; const guild = GuildStore.getGuild(guildId);
const role = guild?.roles[id];
return ( return (
<span style={{ <span style={{
@ -126,7 +125,7 @@ export default definePlugin({
{title ?? label} &mdash; {count} {title ?? label} &mdash; {count}
</span> </span>
); );
}, { noop: true }), },
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) { getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
return { return {

View file

@ -16,7 +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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { ReplyIcon } from "@components/Icons"; import { ReplyIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -27,7 +27,7 @@ import { Message } from "discord-types/general";
const messageUtils = findByPropsLazy("replyToMessage"); const messageUtils = findByPropsLazy("replyToMessage");
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => {
// make sure the message is in the selected channel // make sure the message is in the selected channel
if (SelectedChannelStore.getChannelId() !== message.channel_id) return; if (SelectedChannelStore.getChannelId() !== message.channel_id) return;
const channel = ChannelStore.getChannel(message?.channel_id); const channel = ChannelStore.getChannel(message?.channel_id);
@ -38,7 +38,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
const dmGroup = findGroupChildrenByChildId("pin", children); const dmGroup = findGroupChildrenByChildId("pin", children);
if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) { if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) {
const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin"); const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin");
dmGroup.splice(pinIndex + 1, 0, ( return dmGroup.splice(pinIndex + 1, 0, (
<Menu.MenuItem <Menu.MenuItem
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
@ -46,13 +46,12 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
/> />
)); ));
return;
} }
// servers // servers
const serverGroup = findGroupChildrenByChildId("mark-unread", children); const serverGroup = findGroupChildrenByChildId("mark-unread", children);
if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) { if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) {
serverGroup.unshift(( return serverGroup.unshift((
<Menu.MenuItem <Menu.MenuItem
id="reply" id="reply"
label={i18n.Messages.MESSAGE_ACTION_REPLY} label={i18n.Messages.MESSAGE_ACTION_REPLY}
@ -60,7 +59,6 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag
action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)} action={(e: React.MouseEvent) => messageUtils.replyToMessage(channel, message, e)}
/> />
)); ));
return;
} }
}; };
@ -69,7 +67,12 @@ export default definePlugin({
name: "SearchReply", name: "SearchReply",
description: "Adds a reply button to search results", description: "Adds a reply button to search results",
authors: [Devs.Aria], authors: [Devs.Aria],
contextMenus: {
"message": messageContextMenuPatch start() {
addContextMenuPatch("message", messageContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
} }
}); });

View file

@ -7,7 +7,7 @@
import "./styles.css"; import "./styles.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { getGuildRoles, openImageModal, openUserProfile } from "@utils/discord"; import { openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
@ -172,7 +172,7 @@ function ServerInfoTab({ guild }: GuildProps) {
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?", "Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
"Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`, "Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
"Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category "Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
"Roles": Object.keys(getGuildRoles(guild.id)).length - 1, // - @everyone "Roles": Object.keys(guild.roles).length - 1, // - @everyone
}; };
return ( return (

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
@ -12,7 +12,7 @@ import { Guild } from "discord-types/general";
import { openGuildProfileModal } from "./GuildProfileModal"; import { openGuildProfileModal } from "./GuildProfileModal";
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => { const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => {
const group = findGroupChildrenByChildId("privacy", children); const group = findGroupChildrenByChildId("privacy", children);
group?.push( group?.push(
@ -29,8 +29,12 @@ export default definePlugin({
description: "Allows you to view info about a server by right clicking it in the server list", description: "Allows you to view info about a server by right clicking it in the server list",
authors: [Devs.Ven, Devs.Nuckyz], authors: [Devs.Ven, Devs.Nuckyz],
tags: ["guild", "info"], tags: ["guild", "info"],
contextMenus: {
"guild-context": Patch, start() {
"guild-header-popout": Patch addContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
},
stop() {
removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch);
} }
}); });

View file

@ -4,13 +4,8 @@
} }
.vc-gp-banner { .vc-gp-banner {
cursor: pointer;
aspect-ratio: auto 240 / 135;
height: 334px;
width: 100%; width: 100%;
object-fit: cover; cursor: pointer;
overflow: clip;
overflow-clip-margin: content-box;
} }
.vc-gp-header { .vc-gp-header {

View file

@ -305,27 +305,27 @@ export default definePlugin({
] ]
}, },
{ {
find: '+1]})},"overflow"))', find: ".avatars),children",
replacement: [ replacement: [
{ {
// Create a variable for the channel prop // Create a variable for the channel prop
match: /maxUsers:\i,users:\i.+?}=(\i).*?;/, match: /maxUsers:\i,users:\i.+?=(\i).+?;/,
replace: (m, props) => `${m}let{shcChannel}=${props};` replace: (m, props) => `${m}let{shcChannel}=${props};`
}, },
{ {
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
match: /\i>0(?=&&.{0,60}renderPopout)/, match: /\i>0(?=&&.{0,60}renderPopout)/,
replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})` replace: m => `($self.isHiddenChannel(shcChannel,true)?true:${m})`
}, },
{ {
// Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen // Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/, match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/,
replace: (_, amount) => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?0:1)` replace: (_, amount) => `($self.isHiddenChannel(shcChannel,true)&&${amount}<=0?0:1)`
}, },
{ {
// Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen // Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
match: /(?<="\+",)(\i)\+1/, match: /(?<="\+",)(\i)\+1/,
replace: (m, amount) => `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?"":${m}` replace: (m, amount) => `$self.isHiddenChannel(shcChannel,true)&&${amount}<=0?"":${m}`
} }
] ]
}, },

View file

@ -371,10 +371,6 @@ export function Player() {
if (!track || !device?.is_active || shouldHide) if (!track || !device?.is_active || shouldHide)
return null; return null;
const exportTrackImageStyle = {
"--vc-spotify-track-image": `url(${track?.album?.image?.url || ""})`,
} as React.CSSProperties;
return ( return (
<ErrorBoundary fallback={() => ( <ErrorBoundary fallback={() => (
<div className="vc-spotify-fallback"> <div className="vc-spotify-fallback">
@ -382,7 +378,7 @@ export function Player() {
<p >Check the console for errors</p> <p >Check the console for errors</p>
</div> </div>
)}> )}>
<div id={cl("player")} style={exportTrackImageStyle}> <div id={cl("player")}>
<Info track={track} /> <Info track={track} />
<SeekBar /> <SeekBar />
<Controls /> <Controls />

View file

@ -31,7 +31,7 @@ function toggleHoverControls(value: boolean) {
export default definePlugin({ export default definePlugin({
name: "SpotifyControls", name: "SpotifyControls",
description: "Adds a Spotify player above the account panel", description: "Adds a Spotify player above the account panel",
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000], authors: [Devs.Ven, Devs.afn, Devs.KraXen72],
options: { options: {
hoverControls: { hoverControls: {
description: "Show controls on hover", description: "Show controls on hover",

View file

@ -170,16 +170,9 @@
/* these importants are necessary, it applies a width and height through inline styles */ /* these importants are necessary, it applies a width and height through inline styles */
height: 10px !important; height: 10px !important;
width: 10px !important; width: 10px !important;
margin-top: 4px;
background-color: var(--interactive-normal); background-color: var(--interactive-normal);
border-color: var(--interactive-normal); border-color: var(--interactive-normal);
color: var(--interactive-normal); color: var(--interactive-normal);
opacity: 0;
transition: opacity 0.1s;
}
#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] {
opacity: 1;
} }
#vc-spotify-progress-text { #vc-spotify-progress-text {

View file

@ -7,7 +7,6 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { UserStore } from "@webpack/common";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
superReactByDefault: { superReactByDefault: {
@ -50,7 +49,7 @@ export default definePlugin({
find: ".trackEmojiSearchEmpty,200", find: ".trackEmojiSearchEmpty,200",
replacement: { replacement: {
match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/,
replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.settings.store.superReactByDefault&&${isReactionIntention})`
} }
} }
], ],
@ -60,9 +59,5 @@ export default definePlugin({
if (settings.store.unlimitedSuperReactionPlaying) return true; if (settings.store.unlimitedSuperReactionPlaying) return true;
if (playingCount <= settings.store.superReactionPlayingLimit) return true; if (playingCount <= settings.store.superReactionPlayingLimit) return true;
return false; return false;
},
get shouldSuperReactByDefault() {
return settings.store.superReactByDefault && UserStore.getCurrentUser().premiumType != null;
} }
}); });

View file

@ -19,7 +19,7 @@
import "./styles.css"; import "./styles.css";
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
import { addButton, removeButton } from "@api/MessagePopover"; import { addButton, removeButton } from "@api/MessagePopover";
@ -32,7 +32,7 @@ import { TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon";
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory"; import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
import { translate } from "./utils"; import { translate } from "./utils";
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => { const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => () => {
if (!message.content) return; if (!message.content) return;
const group = findGroupChildrenByChildId("copy-text", children); const group = findGroupChildrenByChildId("copy-text", children);
@ -57,15 +57,13 @@ export default definePlugin({
authors: [Devs.Ven], authors: [Devs.Ven],
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"], dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
settings, settings,
contextMenus: {
"message": messageCtxPatch
},
// not used, just here in case some other plugin wants it or w/e // not used, just here in case some other plugin wants it or w/e
translate, translate,
start() { start() {
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />); addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
addContextMenuPatch("message", messageCtxPatch);
addChatBarButton("vc-translate", TranslateChatBarIcon); addChatBarButton("vc-translate", TranslateChatBarIcon);
addButton("vc-translate", message => { addButton("vc-translate", message => {
@ -93,6 +91,7 @@ export default definePlugin({
stop() { stop() {
removePreSendListener(this.preSend); removePreSendListener(this.preSend);
removeContextMenuPatch("message", messageCtxPatch);
removeChatBarButton("vc-translate"); removeChatBarButton("vc-translate");
removeButton("vc-translate"); removeButton("vc-translate");
removeAccessory("vc-translation"); removeAccessory("vc-translation");

View file

@ -16,7 +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 { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { ImageInvisible, ImageVisible } from "@components/Icons"; import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -24,7 +24,7 @@ import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@web
const EMBED_SUPPRESSED = 1 << 2; const EMBED_SUPPRESSED = 1 << 2;
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => {
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0; const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
if (!isEmbedSuppressed && !embeds.length) return; if (!isEmbedSuppressed && !embeds.length) return;
@ -56,7 +56,12 @@ export default definePlugin({
name: "UnsuppressEmbeds", name: "UnsuppressEmbeds",
authors: [Devs.rad, Devs.HypedDomi], authors: [Devs.rad, Devs.HypedDomi],
description: "Allows you to unsuppress embeds in messages", description: "Allows you to unsuppress embeds in messages",
contextMenus: {
"message": messageContextMenuPatch start() {
} addContextMenuPatch("message", messageContextMenuPatch);
},
stop() {
removeContextMenuPatch("message", messageContextMenuPatch);
},
}); });

View file

@ -19,7 +19,7 @@
import "./index.css"; import "./index.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -30,8 +30,6 @@ import type { ReactNode } from "react";
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
function VencordPopout(onClose: () => void) { function VencordPopout(onClose: () => void) {
const { useQuickCss } = useSettings(["useQuickCss"]);
const pluginEntries = [] as ReactNode[]; const pluginEntries = [] as ReactNode[];
for (const plugin of Object.values(Vencord.Plugins.plugins)) { for (const plugin of Object.values(Vencord.Plugins.plugins)) {
@ -70,10 +68,11 @@ function VencordPopout(onClose: () => void) {
/> />
<Menu.MenuCheckboxItem <Menu.MenuCheckboxItem
id="vc-toolbox-quickcss-toggle" id="vc-toolbox-quickcss-toggle"
checked={useQuickCss} checked={Settings.useQuickCss}
label={"Enable QuickCSS"} label={"Enable QuickCSS"}
action={() => { action={() => {
Settings.useQuickCss = !useQuickCss; Settings.useQuickCss = !Settings.useQuickCss;
onClose();
}} }}
/> />
<Menu.MenuItem <Menu.MenuItem

View file

@ -16,7 +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 { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { ImageIcon } from "@components/Icons"; import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -80,7 +80,7 @@ function openImage(url: string) {
}); });
} }
const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => { const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => {
if (!user) return; if (!user) return;
const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null; const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null;
@ -109,7 +109,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
)); ));
}; };
const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => { const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => () => {
if (!guild) return; if (!guild) return;
const { id, icon, banner } = guild; const { id, icon, banner } = guild;
@ -155,9 +155,14 @@ export default definePlugin({
openImage, openImage,
contextMenus: { start() {
"user-context": UserContext, addContextMenuPatch("user-context", UserContext);
"guild-context": GuildContext addContextMenuPatch("guild-context", GuildContext);
},
stop() {
removeContextMenuPatch("user-context", UserContext);
removeContextMenuPatch("guild-context", GuildContext);
}, },
patches: [ patches: [

View file

@ -16,7 +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 { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { addButton, removeButton } from "@api/MessagePopover"; import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { CodeBlock } from "@components/CodeBlock"; import { CodeBlock } from "@components/CodeBlock";
@ -117,8 +117,8 @@ const settings = definePluginSettings({
} }
}); });
function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback { function MakeContextCallback(name: "Guild" | "User" | "Channel") {
return (children, props) => { const callback: NavContextMenuPatchCallback = (children, props) => () => {
const value = props[name.toLowerCase()]; const value = props[name.toLowerCase()];
if (!value) return; if (!value) return;
if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
@ -141,19 +141,16 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenu
/> />
); );
}; };
return callback;
} }
export default definePlugin({ export default definePlugin({
name: "ViewRaw", name: "ViewRaw",
description: "Copy and view the raw content/data of any message, channel or guild", description: "Copy and view the raw content/data of any message, channel or guild",
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna], authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
dependencies: ["MessagePopoverAPI"], dependencies: ["MessagePopoverAPI"],
settings, settings,
contextMenus: {
"guild-context": MakeContextCallback("Guild"),
"channel-context": MakeContextCallback("Channel"),
"user-context": MakeContextCallback("User")
},
start() { start() {
addButton("ViewRaw", msg => { addButton("ViewRaw", msg => {
@ -190,9 +187,16 @@ export default definePlugin({
onContextMenu: handleContextMenu onContextMenu: handleContextMenu
}; };
}); });
addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
addContextMenuPatch("user-context", MakeContextCallback("User"));
}, },
stop() { stop() {
removeButton("ViewRaw"); removeButton("CopyRawMessage");
removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
removeContextMenuPatch("user-context", MakeContextCallback("User"));
} }
}); });

View file

@ -18,17 +18,15 @@
import "./styles.css"; import "./styles.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Microphone } from "@components/Icons"; import { Microphone } from "@components/Icons";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { chooseFile } from "@utils/web"; import { chooseFile } from "@utils/web";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { Button, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react"; import { ComponentType } from "react";
import { VoiceRecorderDesktop } from "./DesktopRecorder"; import { VoiceRecorderDesktop } from "./DesktopRecorder";
@ -48,30 +46,18 @@ export type VoiceRecorder = ComponentType<{
const VoiceRecorder = IS_DISCORD_DESKTOP ? VoiceRecorderDesktop : VoiceRecorderWeb; const VoiceRecorder = IS_DISCORD_DESKTOP ? VoiceRecorderDesktop : VoiceRecorderWeb;
const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => {
if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return;
children.push(
<Menu.MenuItem
id="vc-send-vmsg"
label={
<div className={OptionClasses.optionLabel}>
<Microphone className={OptionClasses.optionIcon} height={24} width={24} />
<div className={OptionClasses.optionName}>Send voice message</div>
</div>
}
action={() => openModal(modalProps => <Modal modalProps={modalProps} />)}
/>
);
};
export default definePlugin({ export default definePlugin({
name: "VoiceMessages", name: "VoiceMessages",
description: "Allows you to send voice messages like on mobile. To do so, right click the upload button and click Send Voice Message", description: "Allows you to send voice messages like on mobile. To do so, right click the upload button and click Send Voice Message",
authors: [Devs.Ven, Devs.Vap, Devs.Nickyux], authors: [Devs.Ven, Devs.Vap, Devs.Nickyux],
settings, settings,
contextMenus: {
"channel-attach": ctxMenuPatch start() {
addContextMenuPatch("channel-attach", ctxMenuPatch);
},
stop() {
removeContextMenuPatch("channel-attach", ctxMenuPatch);
} }
}); });
@ -178,11 +164,6 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
fallbackValue: EMPTY_META, fallbackValue: EMPTY_META,
}); });
const isUnsupportedFormat = blob && (
!blob.type.startsWith("audio/ogg")
|| blob.type.includes("codecs") && !blob.type.includes("opus")
);
return ( return (
<ModalRoot {...modalProps}> <ModalRoot {...modalProps}>
<ModalHeader> <ModalHeader>
@ -219,16 +200,6 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
recording={isRecording} recording={isRecording}
/> />
{isUnsupportedFormat && (
<Card className={`vc-plugins-restart-card ${Margins.top16}`}>
<Forms.FormText>Voice Messages have to be OggOpus to be playable on iOS. This file is <code>{blob.type}</code> so it will not be playable on iOS.</Forms.FormText>
<Forms.FormText className={Margins.top8}>
To fix it, first convert it to OggOpus, for example using the <Link href="https://convertio.co/mp3-opus/">convertio web converter</Link>
</Forms.FormText>
</Card>
)}
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
@ -246,3 +217,20 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) {
</ModalRoot> </ModalRoot>
); );
} }
const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return;
children.push(
<Menu.MenuItem
id="vc-send-vmsg"
label={
<div className={OptionClasses.optionLabel}>
<Microphone className={OptionClasses.optionIcon} height={24} width={24} />
<div className={OptionClasses.optionName}>Send voice message</div>
</div>
}
action={() => openModal(modalProps => <Modal modalProps={modalProps} />)}
/>
);
};

View file

@ -69,14 +69,14 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
function makeRenderMoreUsers(users: User[]) { function makeRenderMoreUsers(users: User[]) {
return function renderMoreUsers(_label: string, _count: number) { return function renderMoreUsers(_label: string, _count: number) {
return ( return (
<Tooltip text={users.slice(4).map(u => u.username).join(", ")} > <Tooltip text={users.slice(5).map(u => u.username).join(", ")} >
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
<div <div
className={AvatarStyles.moreUsers} className={AvatarStyles.moreUsers}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
> >
+{users.length - 4} +{users.length - 5}
</div> </div>
)} )}
</Tooltip > </Tooltip >

View file

@ -7,7 +7,6 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getGuildRoles } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, PluginNative } from "@utils/types"; import definePlugin, { OptionType, PluginNative } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
@ -197,7 +196,7 @@ export default definePlugin({
if (message.mention_roles.length > 0) { if (message.mention_roles.length > 0) {
for (const roleId of message.mention_roles) { for (const roleId of message.mention_roles) {
const role = getGuildRoles(channel.guild_id)[roleId]; const role = GuildStore.getGuild(channel.guild_id).roles[roleId];
if (!role) continue; if (!role) continue;
const roleColor = role.colorString ?? `#${pingColor}`; const roleColor = role.colorString ?? `#${pingColor}`;
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`); finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);

View file

@ -106,7 +106,7 @@ export async function authorizeCloud() {
try { try {
const res = await fetch(location, { const res = await fetch(location, {
headers: { Accept: "application/json" } headers: new Headers({ Accept: "application/json" })
}); });
const { secret } = await res.json(); const { secret } = await res.json();
if (secret) { if (secret) {

View file

@ -399,10 +399,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "maisy", name: "maisy",
id: 257109471589957632n, id: 257109471589957632n,
}, },
Mopi: {
name: "Mopi",
id: 1022189106614243350n
},
Grzesiek11: { Grzesiek11: {
name: "Grzesiek11", name: "Grzesiek11",
id: 368475654662127616n, id: 368475654662127616n,
@ -414,18 +410,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
coolelectronics: { coolelectronics: {
name: "coolelectronics", name: "coolelectronics",
id: 696392247205298207n, id: 696392247205298207n,
},
Av32000: {
name: "Av32000",
id: 593436735380127770n,
},
Kyuuhachi: {
name: "Kyuuhachi",
id: 236588665420251137n,
},
Elvyra: {
name: "Elvyra",
id: 708275751816003615n,
} }
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);

View file

@ -18,7 +18,7 @@
import { MessageObject } from "@api/MessageEvents"; import { MessageObject } from "@api/MessageEvents";
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Guild, Message, Role, User } from "discord-types/general"; import { Guild, Message, User } from "discord-types/general";
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
@ -185,11 +185,3 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
export function getUniqueUsername(user: User) { export function getUniqueUsername(user: User) {
return user.discriminator === "0" ? user.username : user.tag; return user.discriminator === "0" ? user.username : user.tag;
} }
// FIXME: remove this once discord merges the role change into stable
export function getGuildRoles(guildId: string): Record<string, Role> {
if ("getRoles" in GuildStore)
return (GuildStore as any).getRoles(guildId);
return GuildStore.getGuild(guildId)?.roles ?? {};
}

View file

@ -118,10 +118,10 @@ export async function putCloudSettings(manual?: boolean) {
try { try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), { const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "PUT", method: "PUT",
headers: { headers: new Headers({
Authorization: await getCloudAuth(), Authorization: await getCloudAuth(),
"Content-Type": "application/octet-stream" "Content-Type": "application/octet-stream"
}, }),
body: deflateSync(new TextEncoder().encode(settings)) body: deflateSync(new TextEncoder().encode(settings))
}); });
@ -162,11 +162,11 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
try { try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), { const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "GET", method: "GET",
headers: { headers: new Headers({
Authorization: await getCloudAuth(), Authorization: await getCloudAuth(),
Accept: "application/octet-stream", Accept: "application/octet-stream",
"If-None-Match": Settings.cloud.settingsSyncVersion.toString() "If-None-Match": Settings.cloud.settingsSyncVersion.toString()
}, }),
}); });
if (res.status === 404) { if (res.status === 404) {
@ -251,7 +251,9 @@ export async function deleteCloudSettings() {
try { try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), { const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "DELETE", method: "DELETE",
headers: { Authorization: await getCloudAuth() }, headers: new Headers({
Authorization: await getCloudAuth()
}),
}); });
if (!res.ok) { if (!res.ok) {

View file

@ -17,7 +17,6 @@
*/ */
import { Command } from "@api/Commands"; import { Command } from "@api/Commands";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
@ -116,10 +115,6 @@ export interface PluginDef {
flux?: { flux?: {
[E in FluxEvents]?: (event: any) => void; [E in FluxEvents]?: (event: any) => void;
}; };
/**
* Allows you to manipulate context menus
*/
contextMenus?: Record<string, NavContextMenuPatchCallback>;
/** /**
* Allows you to add custom actions to the Vencord Toolbox. * Allows you to add custom actions to the Vencord Toolbox.
* The key will be used as text for the button * The key will be used as text for the button

View file

@ -51,7 +51,7 @@ export let Avatar: t.Avatar;
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */ /** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
export let useToken: t.useToken; export let useToken: t.useToken;
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)")); export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", m => m?.type?.toString().includes("MASKED_LINK)"));
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format")); export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);

View file

@ -6,10 +6,7 @@
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import * as t from "./types/settingsStores"; export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact");
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame");
export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact") as Record<string, t.SettingsStore>;
export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame") as Record<string, t.SettingsStore>;
export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators"); export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators");

View file

@ -16,11 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export * from "./classes";
export * from "./components"; export * from "./components";
export * from "./fluxEvents"; export * from "./fluxEvents";
export * from "./i18nMessages";
export * from "./menu"; export * from "./menu";
export * from "./settingsStores";
export * from "./stores"; export * from "./stores";
export * from "./utils"; export * from "./utils";

View file

@ -1,11 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export interface SettingsStore<T = any> {
getSetting(): T;
updateSetting(value: T): void;
useSetting(): T;
}

View file

@ -59,7 +59,6 @@ export interface Alerts {
onCancel?(): void; onCancel?(): void;
onConfirm?(): void; onConfirm?(): void;
onConfirmSecondary?(): void; onConfirmSecondary?(): void;
onCloseCallback?(): void;
}): void; }): void;
/** This is a noop, it does nothing. */ /** This is a noop, it does nothing. */
close(): void; close(): void;

View file

@ -60,7 +60,6 @@ export const filters = {
return m => { return m => {
if (filter(m)) return true; if (filter(m)) return true;
if (!m.$$typeof) return false; if (!m.$$typeof) return false;
if (m.type && m.type.render) return filter(m.type.render); // memo + forwardRef
if (m.type) return filter(m.type); // memos if (m.type) return filter(m.type); // memos
if (m.render) return filter(m.render); // forwardRefs if (m.render) return filter(m.render); // forwardRefs
return false; return false;
@ -84,8 +83,8 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
return true; return true;
} }
let devToolsOpen = false;
if (IS_DEV && IS_DISCORD_DESKTOP) { if (IS_DEV && IS_DISCORD_DESKTOP) {
var devToolsOpen = false;
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed // At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
setTimeout(() => { setTimeout(() => {
DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false); DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false);
@ -476,10 +475,8 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback
else if (typeof filter !== "function") else if (typeof filter !== "function")
throw new Error("filter must be a string, string[] or function, got " + typeof filter); throw new Error("filter must be a string, string[] or function, got " + typeof filter);
if (cache != null) { const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true });
const [existing, id] = find(filter, { isIndirect: true, isWaitFor: true });
if (existing) return void callback(existing, id); if (existing) return void callback(existing, id);
}
subscriptions.set(filter, callback); subscriptions.set(filter, callback);
} }