Compare commits

..

4 Commits

Author SHA1 Message Date
Lewis Crichton
5d1736d020 feat: rework consent 2023-10-28 18:07:19 +01:00
Lewis Crichton
3de02708a6 chore: add TODO comment as reminder 2023-10-28 13:23:50 +01:00
Lewis Crichton
49c331fcc9 feat: anonymous telemetry 2023-10-28 13:19:59 +01:00
Lewis Crichton
ba53acdca7 chore: dedupe platform consts 2023-10-28 12:46:48 +01:00
47 changed files with 228 additions and 1012 deletions

View File

@ -9,7 +9,6 @@
"unused-imports",
"path-alias"
],
"extends": ["plugin:react/recommended", "plugin:react/jsx-runtime"],
"settings": {
"import/resolver": {
"alias": {
@ -21,9 +20,6 @@
["@components", "./src/components"]
]
}
},
"react": {
"version": "18.2"
}
},
"rules": {
@ -97,12 +93,6 @@
"unused-imports/no-unused-imports": "error",
"path-alias/no-relative": "error",
"react/no-unescaped-entities": "off",
"react/prop-types": "off",
/* we dont target ancient browsers */
"react/jsx-no-target-blank": "off"
"path-alias/no-relative": "error"
}
}

View File

@ -57,7 +57,6 @@
"eslint": "^8.46.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0",

768
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ import { patches, PMLogger, startAllPlugins } from "./plugins";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { sendTelemetry } from "./utils/telemetry";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common";
@ -83,6 +84,8 @@ async function init() {
syncSettings();
sendTelemetry();
if (!IS_WEB) {
try {
const isOutdated = await checkForUpdates();

View File

@ -21,7 +21,7 @@ import "./styles.css";
import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common";
import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common";
import { NotificationData } from "./Notifications";

View File

@ -61,6 +61,8 @@ export interface Settings {
settingsSync: boolean;
settingsSyncVersion: number;
};
telemetry?: boolean; // tri-state, undefined = ask
}
const DefaultSettings: Settings = {
@ -91,7 +93,9 @@ const DefaultSettings: Settings = {
url: "https://api.vencord.dev/",
settingsSync: false,
settingsSyncVersion: 0
}
},
telemetry: undefined
};
try {

View File

@ -80,7 +80,10 @@ const ErrorBoundary = LazyComponent(() => {
if (this.props.noop) return null;
if (this.props.fallback)
return <this.props.fallback {...this.state}>{this.props.children}</this.props.fallback>;
return <this.props.fallback
children={this.props.children}
{...this.state}
/>;
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";

View File

@ -27,7 +27,7 @@ export function Heart() {
>
<path
fill="#db61a2"
fillRule="evenodd"
fill-rule="evenodd"
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
/>
</svg>

View File

@ -58,7 +58,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fillRule="evenodd">
<g fill="none" fill-rule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} />
</g>

View File

@ -30,7 +30,7 @@ import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";
import { openModal } from "@utils/modal";
import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -95,14 +95,12 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
const isEnabled = () => settings.enabled ?? false;
function openPluginModal() {
openModal(modalProps => (
<PluginModal
{...modalProps}
plugin={plugin}
onRestartNeeded={() => onRestartNeeded(plugin.name)}
/>
));
function openModal() {
openModalLazy(async () => {
return modalProps => {
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
};
});
}
function toggleEnabled() {
@ -161,7 +159,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
infoButton={
<button role="switch" onClick={() => openPluginModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
{plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel />
: <InfoIcon />}
@ -354,7 +352,7 @@ function makeDependencyList(deps: string[]) {
return (
<React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
</React.Fragment>
);
}

View File

@ -106,9 +106,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
}
function renderDiff() {
return diff?.map((p, i) => {
return diff?.map(p => {
const color = p.added ? "lime" : p.removed ? "red" : "grey";
return <div key={i} style={{ color, userSelect: "text" }}>{p.value}</div>;
return <div style={{ color, userSelect: "text" }}>{p.value}</div>;
});
}

View File

@ -26,7 +26,7 @@ import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { findByPropsLazy, findLazy } from "@webpack";
import { Button, Card, FluxDispatcher, Forms, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import { UserThemeHeader } from "main/themes";
import type { ComponentType, Ref, SyntheticEvent } from "react";

View File

@ -57,7 +57,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
title: "Oops!",
body: (
<ErrorCard>
{err.split("\n").map((line, i) => <div key={i}>{Parser.parse(line)}</div>)}
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
</ErrorCard>
)
});
@ -83,7 +83,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
return (
<Card style={{ padding: ".5em" }}>
{updates.map(({ hash, author, message }) => (
<div key={hash}>
<div>
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
<span style={{
marginLeft: "0.5em",

View File

@ -21,6 +21,7 @@ import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton";
import { ErrorCard } from "@components/ErrorCard";
import { isMac, isWindows } from "@utils/constants";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native";
@ -46,9 +47,6 @@ function VencordSettings() {
const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []);
const isWindows = navigator.platform.toLowerCase().startsWith("win");
const isMac = navigator.platform.toLowerCase().startsWith("mac");
const Switches: Array<false | {
key: KeysOfType<typeof settings, boolean>;
title: string;
@ -93,6 +91,11 @@ function VencordSettings() {
key: "macosTranslucency",
title: "Enable translucent window",
note: "Requires a full restart"
},
{
key: "telemetry",
title: "Enable Telemetry",
note: "We only gather anonymous telemetry data. All data deleted after 3 days if you opt out."
}
];

View File

@ -21,30 +21,16 @@ import definePlugin from "@utils/types";
export default definePlugin({
name: "AlwaysAnimate",
description: "Animates anything that can be animated",
description: "Animates anything that can be animated, besides status emojis.",
authors: [Devs.FieryFlames],
patches: [
{
find: "canAnimate:",
find: ".canAnimate",
all: true,
// Some modules match the find but the replacement is returned untouched
noWarn: true,
replacement: {
match: /canAnimate:.+?(?=([,}].*?\)))/g,
replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) return "canAnimate:!0";
return m;
}
}
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"
match: /\.canAnimate\b/g,
replace: ".canAnimate || true"
}
}
]

View File

@ -19,7 +19,7 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { LazyComponent } from "@utils/react";
import { find, findByPropsLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common";
import { React, useStateFromStores } from "@webpack/common";
import { ExpandedGuildFolderStore, settings } from ".";
@ -33,7 +33,6 @@ export default ErrorBoundary.wrap(guildsBarProps => {
<GuildsBar
{...guildsBarProps}
isBetterFolders={true}
betterFoldersExpandedIds={expandedFolders}
/>
);

View File

@ -18,21 +18,13 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import definePlugin, { OptionType } from "@utils/types";
import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
enum FolderIconDisplay {
Never,
Always,
MoreThanOneFolderExpanded
}
const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildFolderStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
@ -40,7 +32,7 @@ let lastGuildId = null as string | null;
let dispatchingFoldersClose = false;
function getGuildFolder(id: string) {
return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
return GuildFolderStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
}
function closeFolders() {
@ -58,6 +50,7 @@ export const settings = definePluginSettings({
sidebarAnim: {
type: OptionType.BOOLEAN,
description: "Animate opening the folder sidebar",
restartNeeded: true,
default: true
},
closeAllFolders: {
@ -86,16 +79,6 @@ export const settings = definePluginSettings({
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
restartNeeded: true,
default: false
},
showFolderIcon: {
type: OptionType.SELECT,
description: "Show the folder icon above the folder guilds in the BetterFolders sidebar",
options: [
{ label: "Never", value: FolderIconDisplay.Never },
{ label: "Always", value: FolderIconDisplay.Always, default: true },
{ label: "When more than one folder is expanded", value: FolderIconDisplay.MoreThanOneFolderExpanded }
],
restartNeeded: true
}
});
@ -116,45 +99,25 @@ export default definePlugin({
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
replace: ",isBetterFolders"
},
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
},
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
{
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
replace: '$&.filter($self.makeGuildsBarGuildListFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))'
},
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
{
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
replace: '$&.filter($self.makeGuildsBarTreeFilter(typeof isBetterFolders!=="undefined"?isBetterFolders:false))'
},
// Export the isBetterFolders variable to the folders component
{
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
}
]
},
{
// This is the parent folder component
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
replacement: [
{
// Modify the expanded state to instead return the list of expanded folders
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
},
// Avoid rendering servers that are not in folders in the Better Folders sidebar
{
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
// Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined
match: /(?<=folderNode:(\i),expanded:)\i(?=,)/,
replace: (isExpandedOrExpandedIds, folderNote) => ""
+ `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`
+ `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?GUILD:)/,
replace: 'if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&$1.parentId==null)return null;'
}
]
},
@ -162,37 +125,33 @@ export default definePlugin({
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
predicate: () => settings.store.sidebar,
replacement: [
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
// Create the isBetterFolders variable in the nested folders component (the parent exports all the props so we don't have to patch it)
{
match: /(?<=let{folderNode:\i,setNodeRef:\i,)/,
replace: "isBetterFolders,"
},
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
{
predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i).+?;)(?=let)/,
replace: '$1=(typeof isBetterFolders!=="undefined"?isBetterFolders:false)?$1:false;'
},
// If we are rendering the Better Folders sidebar, we filter out folders that are not expanded
{
match: /(?=return\(0,\i.\i\)\("div")(?<=selected:\i,expanded:(\i),.+?)/,
replace: (_, expanded) => `if((typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&!${expanded})return null;`
},
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&"
replace: '(typeof isBetterFolders!=="undefined"?isBetterFolders:false)&&'
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{
predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
},
{
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
},
{
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
replace: (m, expanded) => `${m}((typeof isBetterFolders!=="undefined"?isBetterFolders:false)||!${expanded})&&`
}
]
},
@ -253,21 +212,6 @@ export default definePlugin({
}
},
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
const newTree = new GuildsTree();
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
newTree.nodes = Object.fromEntries(
Object.entries(oldTree.nodes)
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
);
return newTree;
},
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
@ -286,21 +230,6 @@ export default definePlugin({
};
},
shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set<any>) {
if (!isBetterFolders) return true;
switch (settings.store.showFolderIcon) {
case FolderIconDisplay.Never:
return false;
case FolderIconDisplay.Always:
return true;
case FolderIconDisplay.MoreThanOneFolderExpanded:
return (expandedFolderIds?.size ?? 0) > 1;
default:
return true;
}
},
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
closeFolders

View File

@ -21,6 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { useTimer } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { React } from "@webpack/common";
function formatDuration(ms: number) {
// here be dragons (moment fucking sucks)

View File

@ -92,7 +92,6 @@ export default definePlugin({
fakeRenderWin = new WeakRef(win);
win.focus();
// eslint-disable-next-line react/no-deprecated
ReactDOM.render(React.createElement(component, props), win.document.body);
}
};

View File

@ -23,7 +23,7 @@ import { isTruthy } from "@utils/guards";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
const ActivityComponent = findByCodeLazy("onOpenGameProfile");
const ActivityClassName = findByPropsLazy("activity", "buttonColor");

View File

@ -205,7 +205,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
alignItems: "center"
}}>
{guilds.map(g => (
<Tooltip key={g.id} text={g.name}>
<Tooltip text={g.name}>
{({ onMouseLeave, onMouseEnter }) => (
<div
onMouseLeave={onMouseLeave}
@ -278,27 +278,25 @@ function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Om
const data = { t: type, ...res } as Sticker | Emoji;
const url = getUrl(data);
return function EmoteClonerModal(modalProps) {
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<img
role="presentation"
aria-hidden
src={url}
alt=""
height={24}
width={24}
style={{ marginRight: "0.5em" }}
/>
<Forms.FormText>Clone {data.name}</Forms.FormText>
</ModalHeader>
<ModalContent>
<CloneModal data={data} />
</ModalContent>
</ModalRoot>
);
};
return modalProps => (
<ModalRoot {...modalProps}>
<ModalHeader>
<img
role="presentation"
aria-hidden
src={url}
alt=""
height={24}
width={24}
style={{ marginRight: "0.5em" }}
/>
<Forms.FormText>Clone {data.name}</Forms.FormText>
</ModalHeader>
<ModalContent>
<CloneModal data={data} />
</ModalContent>
</ModalRoot>
);
})
}
/>

View File

@ -19,7 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants";
import { Devs, isMac } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -96,9 +96,8 @@ export default definePlugin({
],
settingsAboutComponent: () => {
const isMacOS = navigator.platform.includes("Mac");
const modKey = isMacOS ? "cmd" : "ctrl";
const altKey = isMacOS ? "opt" : "alt";
const modKey = isMac ? "cmd" : "ctrl";
const altKey = isMac ? "opt" : "alt";
return (
<React.Fragment>
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>

View File

@ -28,7 +28,7 @@ import style from "./style.css?managed";
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
function makeIcon(showCurrentGame?: boolean) {
return function GameActivityToggle() {
return function () {
return (
<svg
width="20"

View File

@ -343,10 +343,10 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
? parse(message.content)
: [noContent(message.attachments.length, message.embeds.length)]
}
{images.map((a, i) => {
{images.map(a => {
const { width, height } = computeWidthAndHeight(a.width, a.height);
return (
<div key={i}>
<div>
<img src={a.url} width={width} height={height} />
</div>
);

View File

@ -111,7 +111,7 @@ function SettingsComponent(props: { setValue(v: any): void; }) {
return (
<Flex flexDirection="column">
{tags.map(t => (
<Card key={t.name} style={{ padding: "1em 1em 0" }}>
<Card style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => (

View File

@ -64,7 +64,6 @@ export default definePlugin({
renderMutualGDMs(user: User, onClose: () => void) {
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
<Clickable
key={c.id}
className={ProfileListClasses.listRow}
onClick={() => {
onClose();

View File

@ -104,7 +104,6 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
return (
<button
key={permission.id}
className={cl("perms-list-item-btn")}
onClick={() => selectItem(index)}
>
@ -158,7 +157,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
</div>
<div className={cl("perms-perms")}>
{Object.entries(PermissionsBits).map(([permissionName, bit]) => (
<div key={permissionName} className={cl("perms-perms-item")}>
<div className={cl("perms-perms-item")}>
<div className={cl("perms-perms-item-icon")}>
{(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem;

View File

@ -109,7 +109,7 @@ function UserPermissionsComponent({ guild, guildMember }: { guild: Guild; guildM
}
defaultState={settings.store.defaultPermissionsDropdownState}
buttons={[
(<Tooltip key="sort" text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
{tooltipProps => (
<button
{...tooltipProps}
@ -133,7 +133,7 @@ function UserPermissionsComponent({ guild, guildMember }: { guild: Guild; guildM
{userPermissions.length > 0 && (
<div className={classes(root, roles)}>
{userPermissions.map(({ permission, roleColor }) => (
<div key={permission} className={classes(role, rolePill, rolePillBorder)}>
<div className={classes(role, rolePill, rolePillBorder)}>
<div className={roleRemoveButton}>
<span
className={roleCircle}

View File

@ -8,7 +8,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Tooltip } from "@webpack/common";
import { React, Tooltip } from "@webpack/common";
const settings = definePluginSettings({
loop: {

View File

@ -30,23 +30,21 @@ import { User } from "discord-types/general";
const SessionsStore = findStoreLazy("SessionsStore");
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
return function PlatformIndicator({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) {
return (
<Tooltip text={tooltip} >
{(tooltipProps: any) => (
<svg
{...tooltipProps}
height={(opts?.height ?? 20) - (small ? 3 : 0)}
width={(opts?.width ?? 20) - (small ? 3 : 0)}
viewBox={opts?.viewBox ?? "0 0 24 24"}
fill={color}
>
<path d={path} />
</svg>
)}
</Tooltip>
);
};
return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
<Tooltip text={tooltip} >
{(tooltipProps: any) => (
<svg
{...tooltipProps}
height={(opts?.height ?? 20) - (small ? 3 : 0)}
width={(opts?.width ?? 20) - (small ? 3 : 0)}
viewBox={opts?.viewBox ?? "0 0 24 24"}
fill={color}
>
<path d={path} />
</svg>
)}
</Tooltip>
);
}
const Icons = {

View File

@ -17,7 +17,7 @@
*/
import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { Devs, isMac } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
@ -25,7 +25,6 @@ import { Message } from "discord-types/general";
const Kangaroo = findByPropsLazy("jumpToMessage");
const isMac = navigator.platform.includes("Mac"); // bruh
let replyIdx = -1;
let editIdx = -1;

View File

@ -19,7 +19,7 @@
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, ReadStateStore } from "@webpack/common";
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common";
function onClick() {
const channels: Array<any> = [];

View File

@ -172,7 +172,7 @@ export default definePlugin({
height="24"
viewBox="0 0 24 24"
>
<g fill="none" fillRule="evenodd">
<g fill="none" fill-rule="evenodd">
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
<rect width="24" height="24" />
</g>

View File

@ -236,7 +236,6 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
<ScrollerThin fade className={cl("scroller")}>
{members.map(id =>
<FriendRow
key={id}
user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)}

View File

@ -46,7 +46,7 @@ export const Code = ({
.split("\n")
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
} catch {
lines = content.split("\n").map((line, i) => <span key={i}>{line}</span>);
lines = content.split("\n").map(line => <span>{line}</span>);
}
} else {
const renderTokens =
@ -55,11 +55,11 @@ export const Code = ({
.split("\n")
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
lines = renderTokens.map((line, i) => {
lines = renderTokens.map(line => {
// [Cynthia] this makes it so when you highlight the codeblock
// empty lines are also selected and copied when you Ctrl+C.
if (line.length === 0) {
return <span key={i}>{"\n"}</span>;
return <span>{"\n"}</span>;
}
return (

View File

@ -18,7 +18,7 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { useAwaiter, useIntersection } from "@utils/react";
import { hljs } from "@webpack/common";
import { hljs, React } from "@webpack/common";
import { resolveLang } from "../api/languages";
import { shiki } from "../api/shiki";

View File

@ -105,7 +105,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap"
}}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} key={connection.id} />)}
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
</Flex>
</Section>
);

View File

@ -275,7 +275,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<div className="shc-lock-screen-tags-container">
<Text variant="text-lg/bold">Available tags:</Text>
<div className="shc-lock-screen-tags">
{availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
{availableTags.map(tag => <TagComponent tag={tag} />)}
</div>
</div>
}

View File

@ -88,7 +88,7 @@ function SilentMessageToggle(chatBoxProps: {
<g fill="currentColor">
<path d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4C13 3.69264 13.0198 3.3899 13.0582 3.093C12.7147 3.03189 12.3611 3 12 3C8.686 3 6 5.686 6 9V14C6 15.657 4.656 17 3 17V18H21V17C19.344 17 18 15.657 18 14V10.7101ZM8.55493 19C9.24793 20.19 10.5239 21 11.9999 21C13.4759 21 14.7519 20.19 15.4449 19H8.55493Z" />
<path d="M18.2624 5.50209L21 2.5V1H16.0349V2.49791H18.476L16 5.61088V7H21V5.50209H18.2624Z" />
{!enabled && <line x1="22" y1="2" x2="2" y2="22" stroke="var(--red-500)" strokeWidth="2.5" />}
{!enabled && <line x1="22" y1="2" x2="2" y2="22" stroke="var(--red-500)" stroke-width="2.5" />}
</g>
</svg>
</div>

View File

@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, Tooltip } from "@webpack/common";
import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common";
const settings = definePluginSettings({
showIcon: {
@ -62,7 +62,7 @@ function SilentTypingToggle(chatBoxProps: {
<div className={ButtonWrapperClasses.buttonWrapper}>
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" strokeWidth="72" strokeLinecap="round" />}
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
</svg>
</div>
</Button>

View File

@ -38,21 +38,19 @@ function msToHuman(ms: number) {
}
function Svg(path: string, label: string) {
return function Icon() {
return (
<svg
className={classes(cl("button-icon"), cl(label))}
height="24"
width="24"
viewBox="0 0 24 24"
fill="currentColor"
aria-label={label}
focusable={false}
>
<path d={path} />
</svg>
);
};
return () => (
<svg
className={classes(cl("button-icon"), cl(label))}
height="24"
width="24"
viewBox="0 0 24 24"
fill="currentColor"
aria-label={label}
focusable={false}
>
<path d={path} />
</svg>
);
}
// KraXen's icons :yesyes:

View File

@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) {
<Forms.FormSection title="Server Trace" tag="h2">
<code>
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}>
{lines.map((line, i) => (
<span key={i}>{line}</span>
{lines.map(line => (
<span>{line}</span>
))}
</Flex>
</code>

View File

@ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { openUserProfile } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { Avatar, GuildMemberStore, RelationshipStore } from "@webpack/common";
import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common";
import { User } from "discord-types/general";
const settings = definePluginSettings({
@ -135,7 +135,7 @@ export default definePlugin({
return children.map(c =>
c.type === "strong"
? <TypingUser {...props} user={users[element++]} key={users[element].id} />
? <TypingUser {...props} user={users[element++]} />
: c
);
}

View File

@ -18,14 +18,19 @@
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { findLazy, mapMangledModuleLazy } from "@webpack";
import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common";
const KeyBinds = findByPropsLazy("JUMP_TO_GUILD", "SERVER_NEXT");
const GuildNavBinds = mapMangledModuleLazy("mod+alt+down", {
CtrlTab: m => m.binds?.at(-1) === "ctrl+tab",
CtrlShiftTab: m => m.binds?.at(-1) === "ctrl+shift+tab",
});
const DigitBinds = findLazy(m => m.binds?.[0] === "mod+1");
export default definePlugin({
name: "WebKeybinds",
description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,. Only works fully on Vesktop/ArmCord, not inside your browser",
description: "Re-adds keybinds missing in the web version of Discord: ctrl+t, ctrl+shift+t, ctrl+tab, ctrl+shift+tab, ctrl+1-9, ctrl+,",
authors: [Devs.Ven],
enabledByDefault: true,
@ -52,13 +57,13 @@ export default definePlugin({
SettingsRouter.open("My Account");
break;
case "Tab":
const handler = e.shiftKey ? KeyBinds.SERVER_PREV : KeyBinds.SERVER_NEXT;
const handler = e.shiftKey ? GuildNavBinds.CtrlShiftTab : GuildNavBinds.CtrlTab;
handler.action(e);
break;
default:
if (e.key >= "1" && e.key <= "9") {
e.preventDefault();
KeyBinds.JUMP_TO_GUILD.action(e, `mod+${e.key}`);
DigitBinds.action(e, `mod+${e.key}`);
}
break;
}

View File

@ -389,3 +389,10 @@ export const DevsById = /* #__PURE__*/ (() =>
.map(([_, v]) => [v.id, v] as const)
))
)() as Record<string, Dev>;
const { platform } = navigator;
export const isWindows = platform.startsWith("Win");
export const isMac = platform.startsWith("Mac");
export const isLinux = platform.startsWith("Linux");

View File

@ -133,7 +133,7 @@ export function useForceUpdater(withDep?: true) {
export function LazyComponent<T extends object = any>(factory: () => React.ComponentType<T>, attempts = 5) {
const get = makeLazy(factory, attempts);
return function Lazy(props: T) {
return (props: T) => {
const Component = get() ?? NoopComponent;
return <Component {...props} />;
};

69
src/utils/telemetry.tsx Normal file
View File

@ -0,0 +1,69 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Settings } from "@api/Settings";
import { Alerts } from "@webpack/common";
import { isPluginEnabled } from "../plugins";
import { Plugins } from "../Vencord";
import { isLinux, isMac, isWindows } from "./constants";
export function sendTelemetry() {
// TODO: READ THIS CHECK BEFORE RELEASING!!
// if (IS_DEV) return; // don't send on devbuilds, usually contains incorrect data
// if we have not yet told the user about the telemetry's existence, or they haven't agreed at all, DON'T send a
// probe now, but tell them and then let them decide if they want to opt in or not.
if (Settings.telemetry === undefined) {
Alerts.show({
title: "Telemetry Notice",
body: <>
<p>
Vencord has a telemetry feature that sends anonymous data to us, which we use to improve the mod. We
gather your operating system, the version of Vencord you're using and a list of enabled plugins, and
we can use this data to help improve it for yourself and everyone else.
</p>
<p>
If you don't want this, that's okay! We haven't sent anything yet. Please decide if you want to allow
us to gather a little bit of data. You can change this setting at any time in the future. If you
grant consent, we will start sending the data above the next time you reload or restart Discord.
</p>
</>,
confirmText: "Yes, that's fine",
cancelText: "No, I don't want that",
onConfirm() {
Settings.telemetry = true;
},
onCancel() {
Settings.telemetry = false;
}
});
return;
}
// if it's disabled in settings, obviously don't do anything
if (!Settings.telemetry) return;
const activePluginsList = Object.keys(Plugins.plugins)
.filter(p => isPluginEnabled(p));
let operatingSystem = "Unknown";
if (isWindows) operatingSystem = "Windows";
else if (isMac) operatingSystem = "macOS";
else if (isLinux) operatingSystem = "Linux";
const data = {
version: VERSION,
plugins: activePluginsList,
operatingSystem
};
navigator.sendBeacon("https://api.vencord.dev/v1/telemetry", JSON.stringify(data));
}