Compare commits
13 Commits
serverProf
...
v1.4.6
Author | SHA1 | Date | |
---|---|---|---|
|
9b987d1e56 | ||
|
774318d583 | ||
|
7d954f9ade | ||
|
860d6edc7b | ||
|
223b0366c6 | ||
|
69cb7593eb | ||
|
8614e17633 | ||
|
aecd9d8fda | ||
|
faeb4fb585 | ||
|
0d18b44ba7 | ||
|
d671bd65ac | ||
|
8e952c630b | ||
|
2c758ccdf8 |
@ -65,7 +65,7 @@ Also pay attention to the following:
|
||||
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
|
||||
`var .{1,2}=([^;]+);`
|
||||
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
|
||||
- Additionally, as you might have noticed, all of the appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
|
||||
- Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
|
||||
|
||||
#### "replace"
|
||||
|
||||
|
@ -63,7 +63,7 @@ Then fully close Discord from your taskbar or task manager, and restart it. Venc
|
||||
|
||||
If you're using Discord already, go into the `Updater` tab in settings.
|
||||
|
||||
Sometimes it may be neccessary to manually update if the GUI updater fails.
|
||||
Sometimes it may be necessary to manually update if the GUI updater fails.
|
||||
|
||||
To pull latest changes:
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.4.5",
|
||||
"version": "1.4.6",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
@ -89,7 +89,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||
|
||||
const canSubmit = () => Object.values(errors).every(e => !e);
|
||||
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options);
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||
|
||||
React.useEffect(() => {
|
||||
enableStyle(hideBotTagStyle);
|
||||
|
@ -28,7 +28,7 @@ import { SettingsTab } from "@components/VencordSettings/shared";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { openModalLazy } from "@utils/modal";
|
||||
import { LazyComponent, useAwaiter } from "@utils/react";
|
||||
import { Plugin } from "@utils/types";
|
||||
@ -161,7 +161,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
||||
onMouseLeave={onMouseLeave}
|
||||
infoButton={
|
||||
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
||||
{plugin.options
|
||||
{plugin.options && !isObjectEmpty(plugin.options)
|
||||
? <CogWheel />
|
||||
: <InfoIcon width="24" height="24" />}
|
||||
</button>
|
||||
|
@ -49,7 +49,7 @@ export default definePlugin({
|
||||
await this.buildCss();
|
||||
|
||||
addButton("HideAttachments", msg => {
|
||||
if (!msg.attachments.length && !msg.embeds.length) return null;
|
||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
||||
|
||||
const isHidden = hiddenMessages.has(msg.id);
|
||||
|
||||
@ -72,7 +72,7 @@ export default definePlugin({
|
||||
async buildCss() {
|
||||
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
|
||||
style.textContent = `
|
||||
:is(${elements}) [class*="embedWrapper"] {
|
||||
:is(${elements}) :is([class*="embedWrapper", [class*"clickableSticker"]) {
|
||||
/* important is not necessary, but add it to make sure bad themes won't break it */
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
|
||||
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
|
||||
};
|
||||
|
||||
const sharedIntlNumberFormat = new Intl.NumberFormat();
|
||||
const numberFormat = (value: number) => sharedIntlNumberFormat.format(value);
|
||||
|
||||
function MemberCount() {
|
||||
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
|
||||
const { groups } = useStateFromStores(
|
||||
@ -57,7 +60,7 @@ function MemberCount() {
|
||||
alignContent: "center",
|
||||
gap: 0
|
||||
}}>
|
||||
<Tooltip text={`${online} Online in this Channel`} position="bottom">
|
||||
<Tooltip text={`${numberFormat(online)} online in this channel`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span
|
||||
@ -70,11 +73,11 @@ function MemberCount() {
|
||||
marginRight: "0.5em"
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: "var(--green-360)" }}>{online}</span>
|
||||
<span style={{ color: "var(--green-360)" }}>{numberFormat(online)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Tooltip text={`${total} Total Server Members`} position="bottom">
|
||||
<Tooltip text={`${numberFormat(total)} total server members`} position="bottom">
|
||||
{props => (
|
||||
<div {...props}>
|
||||
<span
|
||||
@ -88,7 +91,7 @@ function MemberCount() {
|
||||
marginLeft: "1em"
|
||||
}}
|
||||
/>
|
||||
<span style={{ color: "var(--primary-400)" }}>{total}</span>
|
||||
<span style={{ color: "var(--primary-400)" }}>{numberFormat(total)}</span>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
@ -169,21 +169,14 @@ export default definePlugin({
|
||||
try {
|
||||
if (cache == null || (!isBulk && !cache.has(data.id))) return cache;
|
||||
|
||||
const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
function mutate(id: string) {
|
||||
const mutate = (id: string) => {
|
||||
const msg = cache.get(id);
|
||||
if (!msg) return;
|
||||
|
||||
const EPHEMERAL = 64;
|
||||
const shouldIgnore = data.mlDeleted ||
|
||||
(msg.flags & EPHEMERAL) === EPHEMERAL ||
|
||||
ignoreBots && msg.author?.bot ||
|
||||
ignoreSelf && msg.author?.id === myId ||
|
||||
ignoreUsers.includes(msg.author?.id) ||
|
||||
ignoreChannels.includes(msg.channel_id) ||
|
||||
ignoreGuilds.includes(ChannelStore.getChannel(msg.channel_id)?.guild_id);
|
||||
this.shouldIgnore(msg);
|
||||
|
||||
if (shouldIgnore) {
|
||||
cache = cache.remove(id);
|
||||
@ -192,7 +185,7 @@ export default definePlugin({
|
||||
.set("deleted", true)
|
||||
.set("attachments", m.attachments.map(a => (a.deleted = true, a))));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isBulk) {
|
||||
data.ids.forEach(mutate);
|
||||
@ -205,6 +198,17 @@ export default definePlugin({
|
||||
return cache;
|
||||
},
|
||||
|
||||
shouldIgnore(message: any) {
|
||||
const { ignoreBots, ignoreSelf, ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
return ignoreBots && message.author?.bot ||
|
||||
ignoreSelf && message.author?.id === myId ||
|
||||
ignoreUsers.includes(message.author?.id) ||
|
||||
ignoreChannels.includes(message.channel_id) ||
|
||||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
|
||||
},
|
||||
|
||||
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996
|
||||
patches: [
|
||||
{
|
||||
@ -237,7 +241,7 @@ export default definePlugin({
|
||||
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
|
||||
replace: "$1" +
|
||||
".update($3,m =>" +
|
||||
" (($2.message.flags & 64) === 64 || (Vencord.Settings.plugins.MessageLogger.ignoreBots && $2.message.author?.bot) || (Vencord.Settings.plugins.MessageLogger.ignoreSelf && $2.message.author?.id === Vencord.Webpack.Common.UserStore.getCurrentUser().id)) ? m :" +
|
||||
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
|
||||
" $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" +
|
||||
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
|
||||
" m" +
|
||||
|
@ -122,6 +122,14 @@ export default definePlugin({
|
||||
// ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
|
||||
replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))"
|
||||
}
|
||||
}
|
||||
},
|
||||
// fix alt+shift+up/down
|
||||
{
|
||||
find: '"alt+shift+down"',
|
||||
replacement: {
|
||||
match: /(?<=return \i===\i\.ME\?)\i\.\i\.getPrivateChannelIds\(\)/,
|
||||
replace: "$self.getSnapshot().concat($&.filter(c=>!$self.isPinned(c)))"
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ import { sendBotMessage } from "@api/Commands";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore } from "@webpack/common";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
|
||||
interface Props {
|
||||
type: {
|
||||
@ -31,10 +31,9 @@ interface Props {
|
||||
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
||||
|
||||
export function PreviewButton(chatBoxProps: Props) {
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const draft = getDraft(channelId);
|
||||
|
||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
if (!draft) return null;
|
||||
|
||||
return (
|
||||
|
35
src/plugins/showTimeouts.ts
Normal file
35
src/plugins/showTimeouts.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ShowTimeouts",
|
||||
description: "Display member timeout icons in chat regardless of permissions.",
|
||||
authors: [Devs.Dolfies],
|
||||
patches: [
|
||||
{
|
||||
find: "showCommunicationDisabledStyles",
|
||||
replacement: {
|
||||
match: /&&\i\.\i\.canManageUser\(\i\.\i\.MODERATE_MEMBERS,\i\.author,\i\)/,
|
||||
replace: "",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -16,6 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
@ -25,9 +26,11 @@ import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Forms, Menu, Parser, Text } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
|
||||
|
||||
const CopyIcon = () => {
|
||||
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="22" height="22">
|
||||
@ -60,17 +63,13 @@ function cleanMessage(msg: Message) {
|
||||
|
||||
function CodeBlock(props: { content: string, lang: string; }) {
|
||||
return (
|
||||
// make text selectable
|
||||
<div style={{ userSelect: "text" }}>
|
||||
<div className={CodeContainerClasses.markup}>
|
||||
{Parser.defaultRules.codeBlock.react(props, null, {})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function openViewRawModal(msg: Message) {
|
||||
msg = cleanMessage(msg);
|
||||
const msgJson = JSON.stringify(msg, null, 4);
|
||||
|
||||
function openViewRawModal(json: string, type: string, msgContent?: string) {
|
||||
const key = openModal(props => (
|
||||
<ErrorBoundary>
|
||||
<ModalRoot {...props} size={ModalSize.LARGE}>
|
||||
@ -80,26 +79,28 @@ function openViewRawModal(msg: Message) {
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
{!!msg.content && (
|
||||
{!!msgContent && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
||||
<CodeBlock content={msg.content} lang="" />
|
||||
<CodeBlock content={msgContent} lang="" />
|
||||
<Forms.FormDivider className={Margins.bottom20} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Forms.FormTitle tag="h5">Message Data</Forms.FormTitle>
|
||||
<CodeBlock content={msgJson} lang="json" />
|
||||
<Forms.FormTitle tag="h5">{type} Data</Forms.FormTitle>
|
||||
<CodeBlock content={json} lang="json" />
|
||||
</div>
|
||||
</ModalContent >
|
||||
<ModalFooter>
|
||||
<Flex cellSpacing={10}>
|
||||
<Button onClick={() => copyWithToast(msgJson, "Message data copied to clipboard!")}>
|
||||
Copy Message JSON
|
||||
</Button>
|
||||
<Button onClick={() => copyWithToast(msg.content, "Content copied to clipboard!")}>
|
||||
Copy Raw Content
|
||||
<Button onClick={() => copyWithToast(json, `${type} data copied to clipboard!`)}>
|
||||
Copy {type} JSON
|
||||
</Button>
|
||||
{!!msgContent && (
|
||||
<Button onClick={() => copyWithToast(msgContent, "Content copied to clipboard!")}>
|
||||
Copy Raw Content
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot >
|
||||
@ -107,6 +108,13 @@ function openViewRawModal(msg: Message) {
|
||||
));
|
||||
}
|
||||
|
||||
function openViewRawModalMessage(msg: Message) {
|
||||
msg = cleanMessage(msg);
|
||||
const msgJson = JSON.stringify(msg, null, 4);
|
||||
|
||||
return openViewRawModal(msgJson, "Message", msg.content);
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
clickMethod: {
|
||||
description: "Change the button to view the raw content/data of any message.",
|
||||
@ -118,10 +126,33 @@ const settings = definePluginSettings({
|
||||
}
|
||||
});
|
||||
|
||||
function MakeContextCallback(name: string) {
|
||||
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
const lastChild = children.at(-1);
|
||||
if (lastChild?.key === "developer-actions") {
|
||||
const p = lastChild.props;
|
||||
if (!Array.isArray(p.children))
|
||||
p.children = [p.children];
|
||||
({ children } = p);
|
||||
}
|
||||
|
||||
children.splice(-1, 0,
|
||||
<Menu.MenuItem
|
||||
id={`vc-view-${name.toLowerCase()}-raw`}
|
||||
label="View Raw"
|
||||
action={() => openViewRawModal(JSON.stringify(props[name.toLowerCase()], null, 4), name)}
|
||||
icon={CopyIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "ViewRaw",
|
||||
description: "Copy and view the raw content/data of any message.",
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad],
|
||||
description: "Copy and view the raw content/data of any message, channel or guild",
|
||||
authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna],
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
settings,
|
||||
|
||||
@ -131,7 +162,7 @@ export default definePlugin({
|
||||
if (settings.store.clickMethod === "Right") {
|
||||
copyWithToast(msg.content);
|
||||
} else {
|
||||
openViewRawModal(msg);
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@ -143,7 +174,7 @@ export default definePlugin({
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openViewRawModal(msg);
|
||||
openViewRawModalMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@ -160,9 +191,16 @@ export default definePlugin({
|
||||
onContextMenu: handleContextMenu
|
||||
};
|
||||
});
|
||||
|
||||
addContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
||||
addContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
||||
addContextMenuPatch("user-context", MakeContextCallback("User"));
|
||||
},
|
||||
|
||||
stop() {
|
||||
removeButton("CopyRawMessage");
|
||||
removeContextMenuPatch("guild-context", MakeContextCallback("Guild"));
|
||||
removeContextMenuPatch("channel-context", MakeContextCallback("Channel"));
|
||||
removeContextMenuPatch("user-context", MakeContextCallback("User"));
|
||||
}
|
||||
});
|
||||
|
@ -355,6 +355,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||
name: "bb010g",
|
||||
id: 72791153467990016n,
|
||||
},
|
||||
Dolfies: {
|
||||
name: "Dolfies",
|
||||
id: 852892297661906993n,
|
||||
},
|
||||
RuukuLada: {
|
||||
name: "RuukuLada",
|
||||
id: 119705748346241027n,
|
||||
|
@ -74,6 +74,16 @@ export function isObject(obj: unknown): obj is object {
|
||||
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object is empty or in other words has no own properties
|
||||
*/
|
||||
export function isObjectEmpty(obj: object) {
|
||||
for (const k in obj)
|
||||
if (Object.hasOwn(obj, k)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if value is not a URL, otherwise return URL object.
|
||||
* Avoids having to wrap url checks in a try/catch
|
||||
|
Reference in New Issue
Block a user