mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-25 07:48:32 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
df6a2e5750
16 changed files with 565 additions and 75 deletions
|
@ -21,6 +21,20 @@ const readline = require("readline");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const menu = require("console-menu");
|
const menu = require("console-menu");
|
||||||
|
|
||||||
|
function pathToBranch(dir) {
|
||||||
|
dir = dir.toLowerCase();
|
||||||
|
if (dir.endsWith("development")) {
|
||||||
|
return "development";
|
||||||
|
}
|
||||||
|
if (dir.endsWith("canary")) {
|
||||||
|
return "canary";
|
||||||
|
}
|
||||||
|
if (dir.endsWith("ptb")) {
|
||||||
|
return "ptb";
|
||||||
|
}
|
||||||
|
return "stable";
|
||||||
|
}
|
||||||
|
|
||||||
const BRANCH_NAMES = [
|
const BRANCH_NAMES = [
|
||||||
"Discord",
|
"Discord",
|
||||||
"DiscordPTB",
|
"DiscordPTB",
|
||||||
|
@ -329,6 +343,7 @@ function getLinuxDirs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
pathToBranch,
|
||||||
BRANCH_NAMES,
|
BRANCH_NAMES,
|
||||||
MACOS_DISCORD_DIRS,
|
MACOS_DISCORD_DIRS,
|
||||||
LINUX_DISCORD_DIRS,
|
LINUX_DISCORD_DIRS,
|
||||||
|
|
|
@ -39,7 +39,8 @@ const {
|
||||||
getDarwinDirs,
|
getDarwinDirs,
|
||||||
getLinuxDirs,
|
getLinuxDirs,
|
||||||
ENTRYPOINT,
|
ENTRYPOINT,
|
||||||
question
|
question,
|
||||||
|
pathToBranch
|
||||||
} = require("./common");
|
} = require("./common");
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
|
@ -104,19 +105,84 @@ async function install(installations) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useNewMethod = pathToBranch(selected.branch) !== "stable";
|
||||||
|
|
||||||
for (const version of selected.versions) {
|
for (const version of selected.versions) {
|
||||||
const dir = version.path;
|
|
||||||
|
const dir = useNewMethod ? path.join(version.path, "..") : version.path;
|
||||||
|
|
||||||
// Check if we have write perms to the install directory...
|
// Check if we have write perms to the install directory...
|
||||||
try {
|
try {
|
||||||
fs.accessSync(selected.location, fs.constants.W_OK);
|
fs.accessSync(selected.location, fs.constants.W_OK);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("No write access to", selected.location);
|
console.error("No write access to", selected.location);
|
||||||
console.error(
|
console.error(
|
||||||
"Try running this script as an administrator:",
|
"Make sure Discord isn't running. If that doesn't work,",
|
||||||
|
"try running this script as an administrator:",
|
||||||
"sudo pnpm inject"
|
"sudo pnpm inject"
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
if (useNewMethod) {
|
||||||
|
const appAsar = path.join(dir, "app.asar");
|
||||||
|
const _appAsar = path.join(dir, "_app.asar");
|
||||||
|
|
||||||
|
if (fs.existsSync(_appAsar) && fs.existsSync(appAsar)) {
|
||||||
|
console.log("This copy of Discord already seems to be patched...");
|
||||||
|
console.log("Try running `pnpm uninject` first.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.renameSync(appAsar, _appAsar);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "EBUSY") {
|
||||||
|
console.error(selected.branch, "is still running. Make sure you fully close it before running this script.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.error("Failed to rename app.asar to _app.asar");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(appAsar);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "EBUSY") {
|
||||||
|
console.error(selected.branch, "is still running. Make sure you fully close it before running this script.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.error("Failed to create app.asar folder");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(appAsar, "index.js"),
|
||||||
|
`require("${ENTRYPOINT}");`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(appAsar, "package.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "discord",
|
||||||
|
main: "index.js",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const requiredFiles = ["index.js", "package.json"];
|
||||||
|
|
||||||
|
if (requiredFiles.every(f => fs.existsSync(path.join(appAsar, f)))) {
|
||||||
|
console.log(
|
||||||
|
"Successfully patched",
|
||||||
|
version.name
|
||||||
|
? `${selected.branch} ${version.name}`
|
||||||
|
: selected.branch
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("Failed to patch", dir);
|
||||||
|
console.log("Files in directory:", fs.readdirSync(appAsar));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (fs.existsSync(dir) && fs.lstatSync(dir).isDirectory()) {
|
if (fs.existsSync(dir) && fs.lstatSync(dir).isDirectory()) {
|
||||||
fs.rmSync(dir, { recursive: true });
|
fs.rmSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ const {
|
||||||
getWindowsDirs,
|
getWindowsDirs,
|
||||||
getDarwinDirs,
|
getDarwinDirs,
|
||||||
getLinuxDirs,
|
getLinuxDirs,
|
||||||
|
pathToBranch,
|
||||||
} = require("./common");
|
} = require("./common");
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
|
@ -52,19 +53,56 @@ switch (process.platform) {
|
||||||
async function uninstall(installations) {
|
async function uninstall(installations) {
|
||||||
const selected = await getMenuItem(installations);
|
const selected = await getMenuItem(installations);
|
||||||
|
|
||||||
|
const useNewMethod = pathToBranch(selected.branch) !== "stable";
|
||||||
|
|
||||||
for (const version of selected.versions) {
|
for (const version of selected.versions) {
|
||||||
const dir = version.path;
|
const dir = useNewMethod ? path.join(version.path, "..") : version.path;
|
||||||
|
|
||||||
// Check if we have write perms to the install directory...
|
// Check if we have write perms to the install directory...
|
||||||
try {
|
try {
|
||||||
fs.accessSync(selected.location, fs.constants.W_OK);
|
fs.accessSync(selected.location, fs.constants.W_OK);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("No write access to", selected.location);
|
console.error("No write access to", selected.location);
|
||||||
console.error(
|
console.error(
|
||||||
"Try running this script as an administrator:",
|
"Make sure Discord isn't running. If that doesn't work,",
|
||||||
|
"try running this script as an administrator:",
|
||||||
"sudo pnpm uninject"
|
"sudo pnpm uninject"
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
if (useNewMethod) {
|
||||||
|
if (!fs.existsSync(path.join(dir, "_app.asar"))) {
|
||||||
|
console.error(
|
||||||
|
"Original app.asar (_app.asar) doesn't exist.",
|
||||||
|
"Is your Discord installation corrupt? Try reinstalling Discord."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.join(dir, "app.asar"))) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(path.join(dir, "app.asar"), { force: true, recursive: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to delete app.asar folder");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fs.renameSync(
|
||||||
|
path.join(dir, "_app.asar"),
|
||||||
|
path.join(dir, "app.asar")
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to rename _app.asar to app.asar");
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
"Successfully unpatched",
|
||||||
|
version.name
|
||||||
|
? `${selected.branch} ${version.name}`
|
||||||
|
: selected.branch
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (fs.existsSync(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
fs.rmSync(dir, { recursive: true });
|
fs.rmSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mergeDefaults } from "@utils/misc";
|
import { mergeDefaults } from "@utils/misc";
|
||||||
import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
|
import { SnowflakeUtils } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
|
|
||||||
|
@ -26,9 +27,6 @@ import { Argument } from "./types";
|
||||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||||
const MessageSender = findByPropsLazy("receiveMessage");
|
const MessageSender = findByPropsLazy("receiveMessage");
|
||||||
|
|
||||||
let SnowflakeUtils: any;
|
|
||||||
waitFor("fromTimestamp", m => SnowflakeUtils = m);
|
|
||||||
|
|
||||||
export function generateId() {
|
export function generateId() {
|
||||||
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element;
|
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
||||||
export type Accessory = {
|
export type Accessory = {
|
||||||
callback: AccessoryCallback;
|
callback: AccessoryCallback;
|
||||||
position?: number;
|
position?: number;
|
||||||
|
@ -44,6 +44,15 @@ export function _modifyAccessories(
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const accessory of accessories.values()) {
|
||||||
|
let accessories = accessory.callback(props);
|
||||||
|
if (accessories == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!Array.isArray(accessories))
|
||||||
|
accessories = [accessories];
|
||||||
|
else if (accessories.length === 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
? accessory.position < 0
|
? accessory.position < 0
|
||||||
|
@ -51,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
accessory.callback(props)
|
...accessories.filter(e => e != null) as JSX.Element[]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { useSettings } from "@api/settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { LazyComponent } from "@utils/misc";
|
import { LazyComponent } from "@utils/misc";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||||
import { proxyLazy } from "@utils/proxyLazy";
|
import { proxyLazy } from "@utils/proxyLazy";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
|
@ -84,6 +84,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
|
|
||||||
const canSubmit = () => Object.values(errors).every(e => !e);
|
const canSubmit = () => Object.values(errors).every(e => !e);
|
||||||
|
|
||||||
|
const hasSettings = Boolean(pluginSettings && plugin.options);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const user of plugin.authors.slice(0, 6)) {
|
for (const user of plugin.authors.slice(0, 6)) {
|
||||||
|
@ -121,33 +123,33 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSettings() {
|
function renderSettings() {
|
||||||
if (!pluginSettings || !plugin.options) {
|
if (!hasSettings || !plugin.options) {
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
|
} else {
|
||||||
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
|
function onChange(newValue: any) {
|
||||||
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(hasError: boolean) {
|
||||||
|
setErrors(e => ({ ...e, [key]: hasError }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Component = Components[setting.type];
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
id={key}
|
||||||
|
key={key}
|
||||||
|
option={setting}
|
||||||
|
onChange={onChange}
|
||||||
|
onError={onError}
|
||||||
|
pluginSettings={pluginSettings}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
|
||||||
function onChange(newValue: any) {
|
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError(hasError: boolean) {
|
|
||||||
setErrors(e => ({ ...e, [key]: hasError }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const Component = Components[setting.type];
|
|
||||||
return (
|
|
||||||
<Component
|
|
||||||
id={key}
|
|
||||||
key={key}
|
|
||||||
option={setting}
|
|
||||||
onChange={onChange}
|
|
||||||
onError={onError}
|
|
||||||
pluginSettings={pluginSettings}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMoreUsers(_label: string, count: number) {
|
function renderMoreUsers(_label: string, count: number) {
|
||||||
|
@ -172,14 +174,16 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
|
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
|
||||||
<ModalHeader>
|
<ModalHeader separator={false}>
|
||||||
<Text variant="heading-md/bold">{plugin.name}</Text>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
||||||
|
<ModalCloseButton onClick={onClose} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
|
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
||||||
<Forms.FormText>{plugin.description}</Forms.FormText>
|
<Forms.FormText>{plugin.description}</Forms.FormText>
|
||||||
<div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}>
|
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
||||||
|
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
||||||
<UserSummaryItem
|
<UserSummaryItem
|
||||||
users={authors}
|
users={authors}
|
||||||
count={plugin.authors.length}
|
count={plugin.authors.length}
|
||||||
|
@ -206,13 +210,14 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
{renderSettings()}
|
{renderSettings()}
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter>
|
{hasSettings && <ModalFooter>
|
||||||
<Flex flexDirection="column" style={{ width: "100%" }}>
|
<Flex flexDirection="column" style={{ width: "100%" }}>
|
||||||
<Flex style={{ marginLeft: "auto" }}>
|
<Flex style={{ marginLeft: "auto" }}>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.WHITE}
|
||||||
|
look={Button.Looks.LINK}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -233,7 +238,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
</Flex>
|
</Flex>
|
||||||
{saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>Error while saving: {saveError}</Text>}
|
{saveError && <Text variant="text-md/semibold" style={{ color: "var(--text-danger)" }}>Error while saving: {saveError}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalFooter>
|
</ModalFooter>}
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default definePlugin({
|
||||||
}, {
|
}, {
|
||||||
find: 'type:"user",revision',
|
find: 'type:"user",revision',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\w)\|\|"CONNECTION_OPEN".+?;/g,
|
match: /!(\w{1,3})&&"CONNECTION_OPEN".+?;/g,
|
||||||
replace: "$1=!0;"
|
replace: "$1=!0;"
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
|
315
src/plugins/messageLinkEmbeds.tsx
Normal file
315
src/plugins/messageLinkEmbeds.tsx
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 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 { addAccessory } from "@api/MessageAccessories";
|
||||||
|
import { Settings } from "@api/settings";
|
||||||
|
import { Devs } from "@utils/constants.js";
|
||||||
|
import { Queue } from "@utils/Queue";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ChannelStore,
|
||||||
|
FluxDispatcher,
|
||||||
|
GuildStore,
|
||||||
|
MessageStore,
|
||||||
|
Parser,
|
||||||
|
PermissionStore,
|
||||||
|
RestAPI,
|
||||||
|
Text,
|
||||||
|
UserStore
|
||||||
|
} from "@webpack/common";
|
||||||
|
import { Channel, Guild, Message } from "discord-types/general";
|
||||||
|
|
||||||
|
let messageCache: { [id: string]: { message?: Message, fetched: boolean; }; } = {};
|
||||||
|
|
||||||
|
let AutomodEmbed: React.ComponentType<any>,
|
||||||
|
Embed: React.ComponentType<any>,
|
||||||
|
ChannelMessage: React.ComponentType<any>,
|
||||||
|
Endpoints: Record<string, any>;
|
||||||
|
|
||||||
|
waitFor(["mle_AutomodEmbed"], m => (AutomodEmbed = m.mle_AutomodEmbed));
|
||||||
|
waitFor(filters.byCode("().inlineMediaEmbed"), m => Embed = m);
|
||||||
|
waitFor(m => m.type?.toString()?.includes('["message","compact","className",'), m => ChannelMessage = m);
|
||||||
|
waitFor(["MESSAGE_CREATE_ATTACHMENT_UPLOAD"], _ => Endpoints = _);
|
||||||
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
|
|
||||||
|
const messageFetchQueue = new Queue();
|
||||||
|
async function fetchMessage(channelID: string, messageID: string): Promise<Message | void> {
|
||||||
|
if (messageID in messageCache && !messageCache[messageID].fetched) return Promise.resolve();
|
||||||
|
if (messageCache[messageID]?.fetched) return Promise.resolve(messageCache[messageID].message);
|
||||||
|
|
||||||
|
messageCache[messageID] = { fetched: false };
|
||||||
|
const res = await RestAPI.get({
|
||||||
|
url: Endpoints.MESSAGES(channelID),
|
||||||
|
query: {
|
||||||
|
limit: 1,
|
||||||
|
around: messageID
|
||||||
|
},
|
||||||
|
retries: 2
|
||||||
|
}).catch(() => { });
|
||||||
|
const apiMessage = res.body?.[0];
|
||||||
|
const message: Message = MessageStore.getMessages(apiMessage.channel_id).receiveMessage(apiMessage).get(apiMessage.id);
|
||||||
|
messageCache[message.id] = {
|
||||||
|
message: message,
|
||||||
|
fetched: true
|
||||||
|
};
|
||||||
|
return Promise.resolve(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Attachment {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
url: string;
|
||||||
|
proxyURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTenorGif = /https:\/\/(?:www.)?tenor\.com/;
|
||||||
|
function getImages(message: Message): Attachment[] {
|
||||||
|
const attachments: Attachment[] = [];
|
||||||
|
message.attachments?.forEach(a => {
|
||||||
|
if (a.content_type!.startsWith("image/")) attachments.push({
|
||||||
|
height: a.height!,
|
||||||
|
width: a.width!,
|
||||||
|
url: a.url,
|
||||||
|
proxyURL: a.proxy_url!
|
||||||
|
});
|
||||||
|
});
|
||||||
|
message.embeds?.forEach(e => {
|
||||||
|
if (e.type === "image") attachments.push(
|
||||||
|
e.image ? { ...e.image } : { ...e.thumbnail! }
|
||||||
|
);
|
||||||
|
if (e.type === "gifv" && !isTenorGif.test(e.url!)) {
|
||||||
|
attachments.push({
|
||||||
|
height: e.thumbnail!.height,
|
||||||
|
width: e.thumbnail!.width,
|
||||||
|
url: e.url!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noContent = (attachments: number, embeds: number): string => {
|
||||||
|
if (!attachments && !embeds) return "";
|
||||||
|
if (!attachments) return `[no content, ${embeds} embed${embeds !== 1 ? "s" : ""}]`;
|
||||||
|
if (!embeds) return `[no content, ${attachments} attachment${attachments !== 1 ? "s" : ""}]`;
|
||||||
|
return `[no content, ${attachments} attachment${attachments !== 1 ? "s" : ""} and ${embeds} embed${embeds !== 1 ? "s" : ""}]`;
|
||||||
|
};
|
||||||
|
|
||||||
|
function requiresRichEmbed(message: Message) {
|
||||||
|
if (message.attachments.every(a => a.content_type?.startsWith("image/"))
|
||||||
|
&& message.embeds.every(e => e.type === "image" || (e.type === "gifv" && !isTenorGif.test(e.url!)))
|
||||||
|
&& !message.components.length
|
||||||
|
) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeWidthAndHeight = (width: number, height: number) => {
|
||||||
|
const maxWidth = 400, maxHeight = 300;
|
||||||
|
let newWidth: number, newHeight: number;
|
||||||
|
if (width > height) {
|
||||||
|
newWidth = Math.min(width, maxWidth);
|
||||||
|
newHeight = Math.round(height / (width / newWidth));
|
||||||
|
} else {
|
||||||
|
newHeight = Math.min(height, maxHeight);
|
||||||
|
newWidth = Math.round(width / (height / newHeight));
|
||||||
|
}
|
||||||
|
return { width: newWidth, height: newHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MessageEmbedProps {
|
||||||
|
message: Message;
|
||||||
|
channel: Channel;
|
||||||
|
guildID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MessageLinkEmbeds",
|
||||||
|
description: "Adds a preview to messages that link another message",
|
||||||
|
authors: [Devs.TheSun],
|
||||||
|
dependencies: ["MessageAccessoriesAPI"],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "().embedCard",
|
||||||
|
replacement: [{
|
||||||
|
match: /{"use strict";(.{0,10})\(\)=>(.{1,2})}\);/,
|
||||||
|
replace: '{"use strict";$1()=>$2,me:()=>messageEmbed});'
|
||||||
|
}, {
|
||||||
|
match: /function (.{1,2})\((.{1,2})\){var (.{1,2})=.{1,2}\.message,(.{1,2})=.{1,2}\.channel(.{0,300})\(\)\.embedCard(.{0,500})}\)}/,
|
||||||
|
replace: "function $1($2){var $3=$2.message,$4=$2.channel$5().embedCard$6})}\
|
||||||
|
var messageEmbed={mle_AutomodEmbed:$1};"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
messageBackgroundColor: {
|
||||||
|
description: "Background color for messages in rich embeds",
|
||||||
|
type: OptionType.BOOLEAN
|
||||||
|
},
|
||||||
|
automodEmbeds: {
|
||||||
|
description: "Use automod embeds instead of rich embeds (smaller but less info)",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [{
|
||||||
|
label: "Always use automod embeds",
|
||||||
|
value: "always"
|
||||||
|
}, {
|
||||||
|
label: "Prefer automod embeds, but use rich embeds if some content can't be shown",
|
||||||
|
value: "prefer"
|
||||||
|
}, {
|
||||||
|
label: "Never use automod embeds",
|
||||||
|
value: "never",
|
||||||
|
default: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
clearMessageCache: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
description: "Clear the linked message cache",
|
||||||
|
component: () =>
|
||||||
|
<Button onClick={() => messageCache = {}}>
|
||||||
|
Clear the linked message cache
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
addAccessory("messageLinkEmbed", props => this.messageEmbedAccessory(props), 4 /* just above rich embeds*/);
|
||||||
|
},
|
||||||
|
|
||||||
|
messageLinkRegex: /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,19}|@me)\/(\d{17,19})\/(\d{17,19})/g,
|
||||||
|
|
||||||
|
messageEmbedAccessory(props) {
|
||||||
|
const { message }: { message: Message; } = props;
|
||||||
|
|
||||||
|
const accessories = [] as (JSX.Element | null)[];
|
||||||
|
|
||||||
|
let match = null as RegExpMatchArray | null;
|
||||||
|
while ((match = this.messageLinkRegex.exec(message.content!)) !== null) {
|
||||||
|
const [_, guildID, channelID, messageID] = match;
|
||||||
|
|
||||||
|
const linkedChannel = ChannelStore.getChannel(channelID);
|
||||||
|
if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let linkedMessage = messageCache[messageID]?.message as Message;
|
||||||
|
if (!linkedMessage) {
|
||||||
|
linkedMessage ??= MessageStore.getMessage(channelID, messageID);
|
||||||
|
if (linkedMessage) messageCache[messageID] = { message: linkedMessage, fetched: true };
|
||||||
|
else {
|
||||||
|
const msg = { ...message } as any;
|
||||||
|
delete msg.embeds;
|
||||||
|
messageFetchQueue.push(() => fetchMessage(channelID, messageID)
|
||||||
|
.then(m => m && FluxDispatcher.dispatch({
|
||||||
|
type: "MESSAGE_UPDATE",
|
||||||
|
message: msg
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const messageProps: MessageEmbedProps = {
|
||||||
|
message: linkedMessage,
|
||||||
|
channel: linkedChannel,
|
||||||
|
guildID
|
||||||
|
};
|
||||||
|
|
||||||
|
const type = Settings.plugins[this.name].automodEmbeds;
|
||||||
|
accessories.push(
|
||||||
|
type === "always" || (type === "prefer" && !requiresRichEmbed(linkedMessage))
|
||||||
|
? this.automodEmbedAccessory(messageProps)
|
||||||
|
: this.channelMessageEmbedAccessory(messageProps)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return accessories;
|
||||||
|
},
|
||||||
|
|
||||||
|
channelMessageEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
|
const { message, channel, guildID } = props;
|
||||||
|
|
||||||
|
const isDM = guildID === "@me";
|
||||||
|
const guild = !isDM && GuildStore.getGuild(channel.guild_id);
|
||||||
|
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
||||||
|
const classNames = [SearchResultClasses.message];
|
||||||
|
if (Settings.plugins[this.name].messageBackgroundColor) classNames.push(SearchResultClasses.searchResult);
|
||||||
|
|
||||||
|
return <Embed
|
||||||
|
embed={{
|
||||||
|
rawDescription: "",
|
||||||
|
color: "var(--background-secondary)",
|
||||||
|
author: {
|
||||||
|
name: <Text variant="text-xs/medium" tag="span">
|
||||||
|
{[
|
||||||
|
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>,
|
||||||
|
...(isDM
|
||||||
|
? Parser.parse(`<@${dmReceiver.id}>`)
|
||||||
|
: Parser.parse(`<#${channel.id}>`)
|
||||||
|
)
|
||||||
|
]}
|
||||||
|
</Text>,
|
||||||
|
iconProxyURL: guild
|
||||||
|
? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png`
|
||||||
|
: `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}`
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderDescription={() => {
|
||||||
|
return <div key={message.id} className={classNames.join(" ")} >
|
||||||
|
<ChannelMessage
|
||||||
|
id={`message-link-embeds-${message.id}`}
|
||||||
|
message={message}
|
||||||
|
channel={channel}
|
||||||
|
subscribeToComponentDispatch={false}
|
||||||
|
/>
|
||||||
|
</div >;
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
},
|
||||||
|
|
||||||
|
automodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
|
const { message, channel, guildID } = props;
|
||||||
|
|
||||||
|
const isDM = guildID === "@me";
|
||||||
|
const images = getImages(message);
|
||||||
|
const { parse } = Parser;
|
||||||
|
|
||||||
|
return <AutomodEmbed
|
||||||
|
channel={channel}
|
||||||
|
childrenAccessories={<Text color="text-muted" variant="text-xs/medium" tag="span">
|
||||||
|
{[
|
||||||
|
...(isDM ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) : parse(`<#${channel.id}>`)),
|
||||||
|
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
||||||
|
]}
|
||||||
|
</Text>}
|
||||||
|
compact={false}
|
||||||
|
content={[
|
||||||
|
...(message.content || !(message.attachments.length > images.length)
|
||||||
|
? parse(message.content)
|
||||||
|
: [noContent(message.attachments.length, message.embeds.length)]
|
||||||
|
),
|
||||||
|
...(images.map<JSX.Element>(a => {
|
||||||
|
const { width, height } = computeWidthAndHeight(a.width, a.height);
|
||||||
|
return <div><img src={a.url} width={width} height={height} /></div>;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
]}
|
||||||
|
hideTimestamp={false}
|
||||||
|
message={message}
|
||||||
|
_messageEmbed="automod"
|
||||||
|
/>;
|
||||||
|
},
|
||||||
|
});
|
|
@ -287,7 +287,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Attachment renderer
|
// Attachment renderer
|
||||||
// Module 96063
|
// Module 96063
|
||||||
find: "[\"className\",\"attachment\",\"inlineMedia\"]",
|
find: "[\"className\",\"attachment\",\"inlineMedia\"",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /((\w)\.className,\w=\2\.attachment),/,
|
match: /((\w)\.className,\w=\2\.attachment),/,
|
||||||
|
|
|
@ -35,6 +35,11 @@ export default definePlugin({
|
||||||
{ name: "angry", description: " ヽ(`Д´)ノ" },
|
{ name: "angry", description: " ヽ(`Д´)ノ" },
|
||||||
{ name: "anger", description: " ヽ(o`皿′o)ノ" },
|
{ name: "anger", description: " ヽ(o`皿′o)ノ" },
|
||||||
{ name: "joy", description: " <( ̄︶ ̄)>" },
|
{ name: "joy", description: " <( ̄︶ ̄)>" },
|
||||||
|
{ name: "blush", description: "૮ ˶ᵔ ᵕ ᵔ˶ ა" },
|
||||||
|
{ name: "confused", description: "(•ิ_•ิ)?" },
|
||||||
|
{ name: "sleeping", description: "(ᴗ_ᴗ)" },
|
||||||
|
{ name: "laughing", description: "o(≧▽≦)o" },
|
||||||
|
|
||||||
].map(data => ({
|
].map(data => ({
|
||||||
...data,
|
...data,
|
||||||
options: [OptionalMessageOption],
|
options: [OptionalMessageOption],
|
||||||
|
|
|
@ -16,20 +16,43 @@
|
||||||
* 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 { Settings } from "@api/settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
interface Reply {
|
||||||
|
message: {
|
||||||
|
author: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoReplyMention",
|
name: "NoReplyMention",
|
||||||
description: "Disables reply pings by default",
|
description: "Disables reply pings by default",
|
||||||
authors: [Devs.DustyAngel47],
|
authors: [Devs.DustyAngel47, Devs.axyie],
|
||||||
|
options: {
|
||||||
|
exemptList: {
|
||||||
|
description:
|
||||||
|
"List of users to exempt from this plugin (separated by commas)",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "1234567890123445,1234567890123445",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldMention(reply: Reply) {
|
||||||
|
return Settings.plugins.NoReplyMention.exemptList.includes(
|
||||||
|
reply.message.author.id
|
||||||
|
);
|
||||||
|
},
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "CREATE_PENDING_REPLY:function",
|
find: "CREATE_PENDING_REPLY:function",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /CREATE_PENDING_REPLY:function\((.{1,2})\){/,
|
match: /CREATE_PENDING_REPLY:function\((.{1,2})\){/,
|
||||||
replace: "CREATE_PENDING_REPLY:function($1){$1.shouldMention=false;"
|
replace:
|
||||||
}
|
"CREATE_PENDING_REPLY:function($1){$1.shouldMention=Vencord.Plugins.plugins.NoReplyMention.shouldMention($1);",
|
||||||
}
|
},
|
||||||
]
|
},
|
||||||
|
],
|
||||||
});
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a modification for Discord's desktop app
|
* Vencord, a modification for Discord's desktop app
|
||||||
* Copyright (c) 2022 OpenAsar
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ChannelStore, Text } from "@webpack/common";
|
import { Button, ChannelStore, SnowflakeUtils, Text } from "@webpack/common";
|
||||||
|
|
||||||
const CONNECT = 1048576n;
|
const CONNECT = 1048576n;
|
||||||
const VIEW_CHANNEL = 1024n;
|
const VIEW_CHANNEL = 1024n;
|
||||||
|
@ -117,7 +117,7 @@ export default definePlugin({
|
||||||
const isHidden = this.isHiddenChannel(channel);
|
const isHidden = this.isHiddenChannel(channel);
|
||||||
// check for type again, otherwise it would show it for hidden stage channels
|
// check for type again, otherwise it would show it for hidden stage channels
|
||||||
if (channel.type === 0 && isHidden) {
|
if (channel.type === 0 && isHidden) {
|
||||||
const lastMessageDate = channel.lastActiveTimestamp ? new Date(channel.lastActiveTimestamp).toLocaleString() : null;
|
const lastMessageDate = channel.lastMessageId ? new Date(SnowflakeUtils.extractTimestamp(channel.lastMessageId)).toLocaleString() : null;
|
||||||
openModal(modalProps => (
|
openModal(modalProps => (
|
||||||
<ModalRoot size={ModalSize.SMALL} {...modalProps}>
|
<ModalRoot size={ModalSize.SMALL} {...modalProps}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
|
|
|
@ -21,9 +21,9 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Button, ChannelStore, Forms, Margins, Parser } from "@webpack/common";
|
import { Button, ChannelStore, Forms, Margins, Parser, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,30 +89,33 @@ function openViewRawModal(msg: Message) {
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ModalRoot {...props} size={ModalSize.LARGE}>
|
<ModalRoot {...props} size={ModalSize.LARGE}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<Forms.FormTitle tag="h1">View Raw</Forms.FormTitle>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>View Raw</Text>
|
||||||
<ModalCloseButton onClick={() => closeModal(key)} />
|
<ModalCloseButton onClick={() => closeModal(key)} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent style={{ padding: "1em" }}>
|
<ModalContent>
|
||||||
<Flex style={{ marginBottom: "1em", marginTop: "1em" }}>
|
<div style={{ padding: "16px 0" }}>
|
||||||
<Button onClick={() => copyWithToast(msg.content, "Content copied to clipboard!")}>
|
{!!msg.content && (
|
||||||
Copy Raw Content
|
<>
|
||||||
</Button>
|
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
||||||
|
<CodeBlock content={msg.content} lang="" />
|
||||||
|
<Forms.FormDivider classes={Margins.marginBottom20} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5">Message Data</Forms.FormTitle>
|
||||||
|
<CodeBlock content={msgJson} lang="json" />
|
||||||
|
</div>
|
||||||
|
</ModalContent >
|
||||||
|
<ModalFooter>
|
||||||
|
<Flex cellSpacing={10}>
|
||||||
<Button onClick={() => copyWithToast(msgJson, "Message data copied to clipboard!")}>
|
<Button onClick={() => copyWithToast(msgJson, "Message data copied to clipboard!")}>
|
||||||
Copy Message JSON
|
Copy Message JSON
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => copyWithToast(msg.content, "Content copied to clipboard!")}>
|
||||||
|
Copy Raw Content
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</ModalFooter>
|
||||||
{!!msg.content && (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
|
||||||
<CodeBlock content={msg.content} lang="" />
|
|
||||||
<Forms.FormDivider classes={Margins.marginBottom20} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Message Data</Forms.FormTitle>
|
|
||||||
<CodeBlock content={msgJson} lang="json" />
|
|
||||||
</ModalContent >
|
|
||||||
</ModalRoot >
|
</ModalRoot >
|
||||||
</ErrorBoundary >
|
</ErrorBoundary >
|
||||||
));
|
));
|
||||||
|
|
|
@ -169,4 +169,12 @@ export const Devs = Object.freeze({
|
||||||
name: "Commandtechno",
|
name: "Commandtechno",
|
||||||
id: 296776625432035328n,
|
id: 296776625432035328n,
|
||||||
},
|
},
|
||||||
|
TheSun: {
|
||||||
|
name: "ActuallyTheSun",
|
||||||
|
id: 406028027768733696n
|
||||||
|
},
|
||||||
|
axyie: {
|
||||||
|
name: "'ax",
|
||||||
|
id: 273562710745284628n,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,7 @@ export let UserStore: Stores.UserStore;
|
||||||
export let SelectedChannelStore: Stores.SelectedChannelStore;
|
export let SelectedChannelStore: Stores.SelectedChannelStore;
|
||||||
export let SelectedGuildStore: any;
|
export let SelectedGuildStore: any;
|
||||||
export let ChannelStore: Stores.ChannelStore;
|
export let ChannelStore: Stores.ChannelStore;
|
||||||
|
export let GuildMemberStore: Stores.GuildMemberStore;
|
||||||
export let RelationshipStore: Stores.RelationshipStore & {
|
export let RelationshipStore: Stores.RelationshipStore & {
|
||||||
/** Get the date (as a string) that the relationship was created */
|
/** Get the date (as a string) that the relationship was created */
|
||||||
getSince(userId: string): string;
|
getSince(userId: string): string;
|
||||||
|
@ -75,6 +76,9 @@ export const TextArea = findByCodeLazy("handleSetRef", "textArea") as React.Comp
|
||||||
export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
||||||
export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers"));
|
export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers"));
|
||||||
|
|
||||||
|
export let SnowflakeUtils: { fromTimestamp: (timestamp: number) => string, extractTimestamp: (snowflake: string) => number };
|
||||||
|
waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);
|
||||||
|
|
||||||
export let Parser: any;
|
export let Parser: any;
|
||||||
export let Alerts: {
|
export let Alerts: {
|
||||||
show(alert: {
|
show(alert: {
|
||||||
|
@ -163,6 +167,7 @@ waitFor("getSortedPrivateChannels", m => ChannelStore = m);
|
||||||
waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m);
|
waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m);
|
||||||
waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m);
|
waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m);
|
||||||
waitFor("getGuildCount", m => GuildStore = m);
|
waitFor("getGuildCount", m => GuildStore = m);
|
||||||
|
waitFor(["getMember", "initialize"], m => GuildMemberStore = m);
|
||||||
waitFor("getRelationshipType", m => RelationshipStore = m);
|
waitFor("getRelationshipType", m => RelationshipStore = m);
|
||||||
|
|
||||||
waitFor(["Hovers", "Looks", "Sizes"], m => Button = m);
|
waitFor(["Hovers", "Looks", "Sizes"], m => Button = m);
|
||||||
|
@ -207,7 +212,7 @@ export type TextProps = React.PropsWithChildren & {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
||||||
|
|
||||||
type RC<C> = React.ComponentType<React.PropsWithChildren<C & Record<string, any>>>;
|
type RC<C> = React.ComponentType<React.PropsWithChildren<C & Record<string, any>>>;
|
||||||
interface Menu {
|
interface Menu {
|
||||||
|
|
Loading…
Add table
Reference in a new issue