Merge branch 'main' of coastalcommits.com:CoastalCommitsArchival/github.com-Vendicated-Vencord
This commit is contained in:
commit
5c03ef46d0
44 changed files with 687 additions and 528 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.6.7",
|
"version": "1.6.9",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -118,11 +118,15 @@ const installerBin = await ensureBinary();
|
||||||
|
|
||||||
console.log("Now running Installer...");
|
console.log("Now running Installer...");
|
||||||
|
|
||||||
execFileSync(installerBin, {
|
try {
|
||||||
stdio: "inherit",
|
execFileSync(installerBin, {
|
||||||
env: {
|
stdio: "inherit",
|
||||||
...process.env,
|
env: {
|
||||||
VENCORD_USER_DATA_DIR: BASE_DIR,
|
...process.env,
|
||||||
VENCORD_DEV_INSTALL: "1"
|
VENCORD_USER_DATA_DIR: BASE_DIR,
|
||||||
}
|
VENCORD_DEV_INSTALL: "1"
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
console.error("Something went wrong. Please check the logs above.");
|
||||||
|
}
|
||||||
|
|
4
src/api/ChatButton.css
Normal file
4
src/api/ChatButton.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.vc-chatbar-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
128
src/api/ChatButtons.tsx
Normal file
128
src/api/ChatButtons.tsx
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./ChatButton.css";
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { waitFor } from "@webpack";
|
||||||
|
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
|
import { Channel } from "discord-types/general";
|
||||||
|
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||||
|
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||||
|
|
||||||
|
export interface ChatBarProps {
|
||||||
|
channel: Channel;
|
||||||
|
disabled: boolean;
|
||||||
|
isEmpty: boolean;
|
||||||
|
type: {
|
||||||
|
analyticsName: string;
|
||||||
|
attachments: boolean;
|
||||||
|
autocomplete: {
|
||||||
|
addReactionShortcut: boolean,
|
||||||
|
forceChatLayer: boolean,
|
||||||
|
reactions: boolean;
|
||||||
|
},
|
||||||
|
commands: {
|
||||||
|
enabled: boolean;
|
||||||
|
},
|
||||||
|
drafts: {
|
||||||
|
type: number,
|
||||||
|
commandType: number,
|
||||||
|
autoSave: boolean;
|
||||||
|
},
|
||||||
|
emojis: {
|
||||||
|
button: boolean;
|
||||||
|
},
|
||||||
|
gifs: {
|
||||||
|
button: boolean,
|
||||||
|
allowSending: boolean;
|
||||||
|
},
|
||||||
|
gifts: {
|
||||||
|
button: boolean;
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
requireSendMessages: boolean;
|
||||||
|
},
|
||||||
|
showThreadPromptOnReply: boolean,
|
||||||
|
stickers: {
|
||||||
|
button: boolean,
|
||||||
|
allowSending: boolean,
|
||||||
|
autoSuggest: boolean;
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
allowMentioning: boolean;
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
button: boolean,
|
||||||
|
ignorePreference: boolean,
|
||||||
|
disableEnterToSubmit: boolean,
|
||||||
|
clearOnSubmit: boolean,
|
||||||
|
useDisabledStylesOnSubmit: boolean;
|
||||||
|
},
|
||||||
|
uploadLongMessages: boolean,
|
||||||
|
upsellLongMessages: {
|
||||||
|
iconOnly: boolean;
|
||||||
|
},
|
||||||
|
showCharacterCount: boolean,
|
||||||
|
sedReplace: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
|
const buttonFactories = new Map<string, ChatBarButton>();
|
||||||
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
if (props.disabled) return;
|
||||||
|
|
||||||
|
for (const [key, Button] of buttonFactories) {
|
||||||
|
buttons.push(
|
||||||
|
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
|
||||||
|
<Button {...props} isMainChat={props.type.analyticsName === "normal"} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
||||||
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
|
export interface ChatBarButtonProps {
|
||||||
|
children: ReactNode;
|
||||||
|
tooltip: string;
|
||||||
|
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
|
||||||
|
}
|
||||||
|
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
|
return (
|
||||||
|
<Tooltip text={props.tooltip}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<div className={`expression-picker-chat-input-button ${ChannelTextAreaClasses?.buttonContainer ?? ""} vc-chatbar-button`}>
|
||||||
|
<Button
|
||||||
|
aria-label={props.tooltip}
|
||||||
|
size=""
|
||||||
|
look={ButtonLooks.BLANK}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
|
onClick={props.onClick}
|
||||||
|
onContextMenu={props.onContextMenu}
|
||||||
|
{...props.buttonProps}
|
||||||
|
>
|
||||||
|
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}, { noop: true });
|
|
@ -21,7 +21,7 @@ import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import type { DispatchWithoutAction } from "react";
|
import type { DispatchWithoutAction } from "react";
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
|
||||||
richBody={
|
richBody={
|
||||||
<div className={cl("body")}>
|
<div className={cl("body")}>
|
||||||
{data.body}
|
{data.body}
|
||||||
<Timestamp timestamp={moment(data.timestamp)} className={cl("timestamp")} />
|
<Timestamp timestamp={new Date(data.timestamp)} className={cl("timestamp")} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as $Badges from "./Badges";
|
import * as $Badges from "./Badges";
|
||||||
|
import * as $ChatButtons from "./ChatButtons";
|
||||||
import * as $Commands from "./Commands";
|
import * as $Commands from "./Commands";
|
||||||
import * as $ContextMenu from "./ContextMenu";
|
import * as $ContextMenu from "./ContextMenu";
|
||||||
import * as $DataStore from "./DataStore";
|
import * as $DataStore from "./DataStore";
|
||||||
|
@ -104,3 +105,8 @@ export const Notifications = $Notifications;
|
||||||
* An api allowing you to patch and add/remove items to/from context menus
|
* An api allowing you to patch and add/remove items to/from context menus
|
||||||
*/
|
*/
|
||||||
export const ContextMenu = $ContextMenu;
|
export const ContextMenu = $ContextMenu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An API allowing you to add buttons to the chat input
|
||||||
|
*/
|
||||||
|
export const ChatButtons = $ChatButtons;
|
||||||
|
|
|
@ -129,6 +129,15 @@ if (!IS_VANILLA) {
|
||||||
});
|
});
|
||||||
|
|
||||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
|
|
||||||
|
// Monkey patch commandLine to disable WidgetLayering: Fix DevTools context menus https://github.com/electron/electron/issues/38790
|
||||||
|
const originalAppend = app.commandLine.appendSwitch;
|
||||||
|
app.commandLine.appendSwitch = function (...args) {
|
||||||
|
if (args[0] === "disable-features" && !args[1]?.includes("WidgetLayering")) {
|
||||||
|
args[1] += ",WidgetLayering";
|
||||||
|
}
|
||||||
|
return originalAppend.apply(this, args);
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||||
}
|
}
|
||||||
|
|
22
src/plugins/_api/chatButtons.ts
Normal file
22
src/plugins/_api/chatButtons.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ChatInputButtonAPI",
|
||||||
|
description: "API to add buttons to the chat input",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
|
patches: [{
|
||||||
|
find: 'location:"ChannelTextAreaButtons"',
|
||||||
|
replacement: {
|
||||||
|
match: /if\(!\i\.isMobile\)\{(?=.+?&&(\i)\.push\(.{0,50}"gift")/,
|
||||||
|
replace: "$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
|
@ -140,11 +140,11 @@ export const defaultRules = [
|
||||||
"tt_content",
|
"tt_content",
|
||||||
"lr@yandex.*",
|
"lr@yandex.*",
|
||||||
"redircnt@yandex.*",
|
"redircnt@yandex.*",
|
||||||
"feature@youtube.com",
|
"feature@*.youtube.com",
|
||||||
"kw@youtube.com",
|
"kw@*.youtube.com",
|
||||||
"si@youtube.com",
|
"si@*.youtube.com",
|
||||||
"pp@youtube.com",
|
"pp@*.youtube.com",
|
||||||
"si@youtu.be",
|
"si@*.youtu.be",
|
||||||
"wt_zmc",
|
"wt_zmc",
|
||||||
"utm_source",
|
"utm_source",
|
||||||
"utm_content",
|
"utm_content",
|
||||||
|
|
|
@ -25,7 +25,6 @@ import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { maybePromptToUpdate } from "@utils/updater";
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||||
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||||
import type { ReactElement } from "react";
|
|
||||||
|
|
||||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||||
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
||||||
|
@ -57,13 +56,13 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let crashCount: number = 0;
|
let hasCrashedOnce = false;
|
||||||
let lastCrashTimestamp: number = 0;
|
let isRecovering = false;
|
||||||
let shouldAttemptNextHandle = false;
|
let shouldAttemptRecover = true;
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CrashHandler",
|
name: "CrashHandler",
|
||||||
description: "Utility plugin for handling and possibly recovering from Crashes without a restart",
|
description: "Utility plugin for handling and possibly recovering from crashes without a restart",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
|
|
||||||
|
@ -73,61 +72,67 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?=this\.setState\()/,
|
match: /this\.setState\((.+?)\)/,
|
||||||
replace: "$self.handleCrash(this)||"
|
replace: "$self.handleCrash(this,$1);"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
handleCrash(_this: any, errorState: any) {
|
||||||
if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true;
|
_this.setState(errorState);
|
||||||
|
|
||||||
shouldAttemptNextHandle = false;
|
// Already recovering, prevent error which happens more than once too fast to trigger another recover
|
||||||
|
if (isRecovering) return;
|
||||||
|
isRecovering = true;
|
||||||
|
|
||||||
if (++crashCount > 5) {
|
// 1 ms timeout to avoid react breaking when re-rendering
|
||||||
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
showNotification({
|
// Prevent a crash loop with an error that could not be handled
|
||||||
color: "#eed202",
|
if (!shouldAttemptRecover) {
|
||||||
title: "Discord has crashed!",
|
try {
|
||||||
body: "Awn :( Discord has crashed more than five times, not attempting to recover.",
|
showNotification({
|
||||||
noPersist: true,
|
color: "#eed202",
|
||||||
});
|
title: "Discord has crashed!",
|
||||||
|
body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.",
|
||||||
|
noPersist: true
|
||||||
|
});
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldAttemptRecover = false;
|
||||||
|
// This is enough to avoid a crash loop
|
||||||
|
setTimeout(() => shouldAttemptRecover = true, 500);
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
lastCrashTimestamp = Date.now();
|
try {
|
||||||
return false;
|
if (!hasCrashedOnce) {
|
||||||
}
|
hasCrashedOnce = true;
|
||||||
|
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
|
||||||
setTimeout(() => crashCount--, 60_000);
|
try {
|
||||||
|
if (settings.store.attemptToPreventCrashes) {
|
||||||
try {
|
this.handlePreventCrash(_this);
|
||||||
if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
}
|
||||||
|
} catch (err) {
|
||||||
if (settings.store.attemptToPreventCrashes) {
|
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||||
this.handlePreventCrash(_this);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}, 1);
|
||||||
return false;
|
|
||||||
} catch (err) {
|
|
||||||
CrashHandlerLogger.error("Failed to handle crash", err);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
lastCrashTimestamp = Date.now();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
handlePreventCrash(_this: any) {
|
||||||
if (Date.now() - lastCrashTimestamp >= 1_000) {
|
try {
|
||||||
try {
|
showNotification({
|
||||||
showNotification({
|
color: "#eed202",
|
||||||
color: "#eed202",
|
title: "Discord has crashed!",
|
||||||
title: "Discord has crashed!",
|
body: "Attempting to recover...",
|
||||||
body: "Attempting to recover...",
|
noPersist: true
|
||||||
noPersist: true,
|
});
|
||||||
});
|
} catch { }
|
||||||
} catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const channelId = SelectedChannelStore.getChannelId();
|
const channelId = SelectedChannelStore.getChannelId();
|
||||||
|
@ -176,9 +181,12 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens
|
||||||
|
setImmediate(() => isRecovering = false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shouldAttemptNextHandle = true;
|
_this.setState({ error: null, info: null });
|
||||||
_this.forceUpdate();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,18 +16,27 @@
|
||||||
* 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 { migratePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "DisableDMCallIdle",
|
name: "DisableCallIdle",
|
||||||
description: "Disables automatically getting kicked from a DM voice call after 3 minutes.",
|
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
match: /,?(?=this\.idleTimeout=new \i\.Timeout)/,
|
||||||
|
replace: ";return;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "handleIdleUpdate(){",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=_initialize\(\){)/,
|
||||||
replace: "return;"
|
replace: "return;"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -108,6 +108,7 @@ const enum FakeNoticeType {
|
||||||
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
|
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
|
||||||
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
|
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
|
||||||
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
|
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
|
||||||
|
const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/;
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
enableEmojiBypass: {
|
enableEmojiBypass: {
|
||||||
|
@ -156,6 +157,11 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true,
|
default: true,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
useHyperLinks: {
|
||||||
|
description: "Whether to use hyperlinks when sending fake emojis and stickers",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -345,7 +351,7 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.transformEmojis,
|
predicate: () => settings.store.transformEmojis,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Add the fake nitro emoji notice
|
// Add the fake nitro emoji notice
|
||||||
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
||||||
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -447,13 +453,23 @@ export default definePlugin({
|
||||||
|
|
||||||
trimContent(content: Array<any>) {
|
trimContent(content: Array<any>) {
|
||||||
const firstContent = content[0];
|
const firstContent = content[0];
|
||||||
if (typeof firstContent === "string") content[0] = firstContent.trimStart();
|
if (typeof firstContent === "string") {
|
||||||
if (content[0] === "") content.shift();
|
content[0] = firstContent.trimStart();
|
||||||
|
content[0] || content.shift();
|
||||||
|
} else if (firstContent?.type === "span") {
|
||||||
|
firstContent.props.children = firstContent.props.children.trimStart();
|
||||||
|
firstContent.props.children || content.shift();
|
||||||
|
}
|
||||||
|
|
||||||
const lastIndex = content.length - 1;
|
const lastIndex = content.length - 1;
|
||||||
const lastContent = content[lastIndex];
|
const lastContent = content[lastIndex];
|
||||||
if (typeof lastContent === "string") content[lastIndex] = lastContent.trimEnd();
|
if (typeof lastContent === "string") {
|
||||||
if (content[lastIndex] === "") content.pop();
|
content[lastIndex] = lastContent.trimEnd();
|
||||||
|
content[lastIndex] || content.pop();
|
||||||
|
} else if (lastContent?.type === "span") {
|
||||||
|
lastContent.props.children = lastContent.props.children.trimEnd();
|
||||||
|
lastContent.props.children || content.pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clearEmptyArrayItems(array: Array<any>) {
|
clearEmptyArrayItems(array: Array<any>) {
|
||||||
|
@ -465,7 +481,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
|
patchFakeNitroEmojisOrRemoveStickersLinks(content: Array<any>, inline: boolean) {
|
||||||
// If content has more than one child or it's a single ReactElement like a header or list
|
// If content has more than one child or it's a single ReactElement like a header, list or span
|
||||||
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
|
if ((content.length > 1 || typeof content[0]?.type === "string") && !settings.store.transformCompoundSentence) return content;
|
||||||
|
|
||||||
let nextIndex = content.length;
|
let nextIndex = content.length;
|
||||||
|
@ -574,7 +590,7 @@ export default definePlugin({
|
||||||
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
|
itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url));
|
||||||
|
|
||||||
for (const item of itemsToMaybePush) {
|
for (const item of itemsToMaybePush) {
|
||||||
if (!settings.store.transformCompoundSentence && !item.startsWith("http")) continue;
|
if (!settings.store.transformCompoundSentence && !item.startsWith("http") && !hyperLinkRegex.test(item)) continue;
|
||||||
|
|
||||||
const imgMatch = item.match(fakeNitroStickerRegex);
|
const imgMatch = item.match(fakeNitroStickerRegex);
|
||||||
if (imgMatch) {
|
if (imgMatch) {
|
||||||
|
@ -619,8 +635,7 @@ export default definePlugin({
|
||||||
case "image": {
|
case "image": {
|
||||||
if (
|
if (
|
||||||
!settings.store.transformCompoundSentence
|
!settings.store.transformCompoundSentence
|
||||||
&& !contentItems.includes(embed.url!)
|
&& !contentItems.some(item => item === embed.url! || item.match(hyperLinkRegex)?.[1] === embed.url!)
|
||||||
&& !contentItems.includes(embed.image?.proxyURL!)
|
|
||||||
) return false;
|
) return false;
|
||||||
|
|
||||||
if (settings.store.transformEmojis) {
|
if (settings.store.transformEmojis) {
|
||||||
|
@ -698,7 +713,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
getStickerLink(stickerId: string) {
|
getStickerLink(stickerId: string) {
|
||||||
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`;
|
return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
||||||
|
@ -795,12 +810,16 @@ export default definePlugin({
|
||||||
if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
|
if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
|
||||||
link = link.replace(".png", ".gif");
|
link = link.replace(".png", ".gif");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sticker.format_type === StickerType.APNG) {
|
if (sticker.format_type === StickerType.APNG) {
|
||||||
this.sendAnimatedSticker(link, sticker.id, channelId);
|
this.sendAnimatedSticker(link, sticker.id, channelId);
|
||||||
return { cancel: true };
|
return { cancel: true };
|
||||||
} else {
|
} else {
|
||||||
|
const url = new URL(link);
|
||||||
|
url.searchParams.set("name", sticker.name);
|
||||||
|
|
||||||
|
messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`;
|
||||||
extra.stickers!.length = 0;
|
extra.stickers!.length = 0;
|
||||||
messageObj.content += ` ${link}&name=${encodeURIComponent(sticker.name)}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,12 +832,13 @@ export default definePlugin({
|
||||||
if (emoji.guildId === guildId && !emoji.animated) continue;
|
if (emoji.guildId === guildId && !emoji.animated) continue;
|
||||||
|
|
||||||
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
||||||
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
|
||||||
size: Settings.plugins.FakeNitro.emojiSize,
|
const url = new URL(emoji.url);
|
||||||
name: encodeURIComponent(emoji.name)
|
url.searchParams.set("size", s.emojiSize.toString());
|
||||||
}));
|
url.searchParams.set("name", emoji.name);
|
||||||
|
|
||||||
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
||||||
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
|
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -840,11 +860,11 @@ export default definePlugin({
|
||||||
if (emoji.available !== false && canUseEmotes) return emojiStr;
|
if (emoji.available !== false && canUseEmotes) return emojiStr;
|
||||||
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
|
if (emoji.guildId === guildId && !emoji.animated) return emojiStr;
|
||||||
|
|
||||||
const url = emoji.url.replace(/\?size=\d+/, "?" + new URLSearchParams({
|
const url = new URL(emoji.url);
|
||||||
size: Settings.plugins.FakeNitro.emojiSize,
|
url.searchParams.set("size", s.emojiSize.toString());
|
||||||
name: encodeURIComponent(emoji.name)
|
url.searchParams.set("name", emoji.name);
|
||||||
}));
|
|
||||||
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,8 +16,9 @@ app.on("browser-window-created", (_, win) => {
|
||||||
|
|
||||||
frame.executeJavaScript(`
|
frame.executeJavaScript(`
|
||||||
new MutationObserver(() => {
|
new MutationObserver(() => {
|
||||||
let err = document.querySelector(".ytp-error-content-wrap-subreason span")?.textContent;
|
if(
|
||||||
if (err && err.includes("blocked it from display")) window.location.reload()
|
document.querySelector('div.ytp-error-content-wrap-subreason a[href*="www.youtube.com/watch?v="]')
|
||||||
|
) location.reload()
|
||||||
}).observe(document.body, { childList: true, subtree:true });
|
}).observe(document.body, { childList: true, subtree:true });
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,14 +123,13 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
||||||
waitFor(() => instance.state.readyState === "READY", () => {
|
waitFor(() => instance.state.readyState === "READY", () => {
|
||||||
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
|
const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement;
|
||||||
element.current = elem;
|
element.current = elem;
|
||||||
elem.firstElementChild!.setAttribute("draggable", "false");
|
elem.querySelector("img,video")?.setAttribute("draggable", "false");
|
||||||
if (instance.props.animated) {
|
if (instance.props.animated) {
|
||||||
originalVideoElementRef.current = elem!.querySelector("video")!;
|
originalVideoElementRef.current = elem!.querySelector("video")!;
|
||||||
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
|
originalVideoElementRef.current.addEventListener("timeupdate", syncVideos);
|
||||||
setReady(true);
|
|
||||||
} else {
|
|
||||||
setReady(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setReady(true);
|
||||||
});
|
});
|
||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
document.addEventListener("keyup", onKeyUp);
|
document.addEventListener("keyup", onKeyUp);
|
||||||
|
@ -155,7 +154,9 @@ export const Magnifier: React.FC<MagnifierProps> = ({ instance, size: initialSiz
|
||||||
|
|
||||||
if (!ready) return null;
|
if (!ready) return null;
|
||||||
|
|
||||||
const box = element.current!.getBoundingClientRect();
|
const box = element.current?.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (!box) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -171,7 +171,7 @@ export default definePlugin({
|
||||||
find: "handleImageLoad=",
|
find: "handleImageLoad=",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /showThumbhashPlaceholder:\i,/,
|
match: /placeholderVersion:\i,/,
|
||||||
replace: "...$self.makeProps(this),$&"
|
replace: "...$self.makeProps(this),$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getStegCloak } from "@utils/dependencies";
|
import { getStegCloak } from "@utils/dependencies";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
import { buildDecModal } from "./components/DecryptionModal";
|
import { buildDecModal } from "./components/DecryptionModal";
|
||||||
|
@ -64,54 +65,31 @@ function Indicator() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatBarIcon(chatBoxProps: {
|
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
type: {
|
if (!isMainChat) return null;
|
||||||
analyticsName: string;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text="Encrypt Message">
|
<ChatBarButton
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
tooltip="Encrypt Message"
|
||||||
// size="" = Button.Sizes.NONE
|
onClick={() => buildEncModal()}
|
||||||
/*
|
|
||||||
many themes set "> button" to display: none, as the gift button is
|
buttonProps={{
|
||||||
the only directly descending button (all the other elements are divs.)
|
"aria-haspopup": "dialog",
|
||||||
Thus, wrap in a div here to avoid getting hidden by that.
|
}}
|
||||||
flex is for some reason necessary as otherwise the button goes flying off
|
>
|
||||||
*/
|
<svg
|
||||||
<div style={{ display: "flex" }}>
|
aria-hidden
|
||||||
<Button
|
role="img"
|
||||||
aria-haspopup="dialog"
|
width="24"
|
||||||
aria-label="Encrypt Message"
|
height="24"
|
||||||
size=""
|
viewBox={"0 0 64 64"}
|
||||||
look={ButtonLooks.BLANK}
|
style={{ scale: "1.39", translate: "0 -1px" }}
|
||||||
onMouseEnter={onMouseEnter}
|
>
|
||||||
onMouseLeave={onMouseLeave}
|
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
</svg>
|
||||||
onClick={() => buildEncModal()}
|
</ChatBarButton>
|
||||||
style={{ padding: "0 2px", scale: "0.9" }}
|
|
||||||
>
|
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
|
||||||
<svg
|
|
||||||
aria-hidden
|
|
||||||
role="img"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
viewBox={"0 0 64 64"}
|
|
||||||
style={{ scale: "1.1" }}
|
|
||||||
>
|
|
||||||
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Tooltip >
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
savedPasswords: {
|
savedPasswords: {
|
||||||
|
@ -125,7 +103,7 @@ export default definePlugin({
|
||||||
name: "InvisibleChat",
|
name: "InvisibleChat",
|
||||||
description: "Encrypt your Messages in a non-suspicious way!",
|
description: "Encrypt your Messages in a non-suspicious way!",
|
||||||
authors: [Devs.SammCheese],
|
authors: [Devs.SammCheese],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// Indicator
|
// Indicator
|
||||||
|
@ -135,13 +113,6 @@ export default definePlugin({
|
||||||
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: "ChannelTextAreaButtons",
|
|
||||||
replacement: {
|
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
EMBED_API_URL: "https://embed.sammcheese.net",
|
EMBED_API_URL: "https://embed.sammcheese.net",
|
||||||
|
@ -151,10 +122,7 @@ export default definePlugin({
|
||||||
),
|
),
|
||||||
settings,
|
settings,
|
||||||
async start() {
|
async start() {
|
||||||
const { default: StegCloak } = await getStegCloak();
|
addButton("InvisibleChat", message => {
|
||||||
steggo = new StegCloak(true, false);
|
|
||||||
|
|
||||||
addButton("invDecrypt", message => {
|
|
||||||
return this.INV_REGEX.test(message?.content)
|
return this.INV_REGEX.test(message?.content)
|
||||||
? {
|
? {
|
||||||
label: "Decrypt Message",
|
label: "Decrypt Message",
|
||||||
|
@ -170,10 +138,16 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addChatBarButton("InvisibleChat", ChatBarIcon);
|
||||||
|
|
||||||
|
const { default: StegCloak } = await getStegCloak();
|
||||||
|
steggo = new StegCloak(true, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeButton("invDecrypt");
|
removeButton("InvisibleChat");
|
||||||
|
removeButton("InvisibleChat");
|
||||||
},
|
},
|
||||||
|
|
||||||
// Gets the Embed of a Link
|
// Gets the Embed of a Link
|
||||||
|
@ -216,7 +190,6 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }),
|
|
||||||
popOverIcon: () => <PopOverIcon />,
|
popOverIcon: () => <PopOverIcon />,
|
||||||
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,15 +29,17 @@ import {
|
||||||
ChannelStore,
|
ChannelStore,
|
||||||
FluxDispatcher,
|
FluxDispatcher,
|
||||||
GuildStore,
|
GuildStore,
|
||||||
|
IconUtils,
|
||||||
MessageStore,
|
MessageStore,
|
||||||
Parser,
|
Parser,
|
||||||
|
PermissionsBits,
|
||||||
PermissionStore,
|
PermissionStore,
|
||||||
RestAPI,
|
RestAPI,
|
||||||
Text,
|
Text,
|
||||||
TextAndImagesSettingsStores,
|
TextAndImagesSettingsStores,
|
||||||
UserStore
|
UserStore
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
import { Channel, Guild, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
|
||||||
const messageCache = new Map<string, {
|
const messageCache = new Map<string, {
|
||||||
message?: Message;
|
message?: Message;
|
||||||
|
@ -49,8 +51,9 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo
|
||||||
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)");
|
||||||
|
|
||||||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||||
|
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
||||||
|
|
||||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||||
|
|
||||||
interface Attachment {
|
interface Attachment {
|
||||||
|
@ -63,7 +66,6 @@ interface Attachment {
|
||||||
interface MessageEmbedProps {
|
interface MessageEmbedProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
guildID: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageFetchQueue = new Queue();
|
const messageFetchQueue = new Queue();
|
||||||
|
@ -226,19 +228,19 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
|
|
||||||
let match = null as RegExpMatchArray | null;
|
let match = null as RegExpMatchArray | null;
|
||||||
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
||||||
const [_, guildID, channelID, messageID] = match;
|
const [_, channelID, messageID] = match;
|
||||||
if (embeddedBy.includes(messageID)) {
|
if (embeddedBy.includes(messageID)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkedChannel = ChannelStore.getChannel(channelID);
|
const linkedChannel = ChannelStore.getChannel(channelID);
|
||||||
if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) {
|
if (!linkedChannel || (!linkedChannel.isPrivate() && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, linkedChannel))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { listMode, idList } = settings.store;
|
const { listMode, idList } = settings.store;
|
||||||
|
|
||||||
const isListed = [guildID, channelID, message.author.id].some(id => id && idList.includes(id));
|
const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id));
|
||||||
|
|
||||||
if (listMode === "blacklist" && isListed) continue;
|
if (listMode === "blacklist" && isListed) continue;
|
||||||
if (listMode === "whitelist" && !isListed) continue;
|
if (listMode === "whitelist" && !isListed) continue;
|
||||||
|
@ -265,8 +267,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
|
|
||||||
const messageProps: MessageEmbedProps = {
|
const messageProps: MessageEmbedProps = {
|
||||||
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
|
message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]),
|
||||||
channel: linkedChannel,
|
channel: linkedChannel
|
||||||
guildID
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const type = settings.store.automodEmbeds;
|
const type = settings.store.automodEmbeds;
|
||||||
|
@ -280,59 +281,64 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
return accessories.length ? <>{accessories}</> : null;
|
return accessories.length ? <>{accessories}</> : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null {
|
function getChannelLabelAndIconUrl(channel: Channel) {
|
||||||
const isDM = guildID === "@me";
|
if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))];
|
||||||
|
if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)];
|
||||||
|
return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))];
|
||||||
|
}
|
||||||
|
|
||||||
const guild = !isDM && GuildStore.getGuild(channel.guild_id);
|
function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null {
|
||||||
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]);
|
||||||
|
|
||||||
|
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
||||||
|
|
||||||
return <Embed
|
return (
|
||||||
embed={{
|
<Embed
|
||||||
rawDescription: "",
|
embed={{
|
||||||
color: "var(--background-secondary)",
|
rawDescription: "",
|
||||||
author: {
|
color: "var(--background-secondary)",
|
||||||
name: <Text variant="text-xs/medium" tag="span">
|
author: {
|
||||||
<span>{isDM ? "Direct Message - " : (guild as Guild).name + " - "}</span>
|
name: <Text variant="text-xs/medium" tag="span">
|
||||||
{isDM
|
<span>{channelLabel} - </span>
|
||||||
? Parser.parse(`<@${dmReceiver.id}>`)
|
{Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)}
|
||||||
: Parser.parse(`<#${channel.id}>`)
|
</Text>,
|
||||||
}
|
iconProxyURL: iconUrl
|
||||||
</Text>,
|
}
|
||||||
iconProxyURL: guild
|
}}
|
||||||
? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png`
|
renderDescription={() => (
|
||||||
: `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}`
|
<div key={message.id} className={classes(SearchResultClasses.message, settings.store.messageBackgroundColor && SearchResultClasses.searchResult)}>
|
||||||
}
|
<ChannelMessage
|
||||||
}}
|
id={`message-link-embeds-${message.id}`}
|
||||||
renderDescription={() => (
|
message={message}
|
||||||
<div key={message.id} className={classes(SearchResultClasses.message, settings.store.messageBackgroundColor && SearchResultClasses.searchResult)}>
|
channel={channel}
|
||||||
<ChannelMessage
|
subscribeToComponentDispatch={false}
|
||||||
id={`message-link-embeds-${message.id}`}
|
/>
|
||||||
message={message}
|
</div>
|
||||||
channel={channel}
|
)}
|
||||||
subscribeToComponentDispatch={false}
|
/>
|
||||||
/>
|
);
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
const { message, channel, guildID } = props;
|
const { message, channel } = props;
|
||||||
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
||||||
const isDM = guildID === "@me";
|
|
||||||
const images = getImages(message);
|
const images = getImages(message);
|
||||||
const { parse } = Parser;
|
const { parse } = Parser;
|
||||||
|
|
||||||
|
const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel);
|
||||||
|
|
||||||
return <AutoModEmbed
|
return <AutoModEmbed
|
||||||
channel={channel}
|
channel={channel}
|
||||||
childrenAccessories={
|
childrenAccessories={
|
||||||
<Text color="text-muted" variant="text-xs/medium" tag="span">
|
<Text color="text-muted" variant="text-xs/medium" tag="span" className={`${EmbedClasses.embedAuthor} ${EmbedClasses.embedMargin}`}>
|
||||||
{isDM
|
{iconUrl && <img src={iconUrl} className={EmbedClasses.embedAuthorIcon} alt="" />}
|
||||||
? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
<span>
|
||||||
: parse(`<#${channel.id}>`)
|
<span>{channelLabel} - </span>
|
||||||
}
|
{channel.isDM()
|
||||||
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`)
|
||||||
|
: Parser.parse(`<#${channel.id}>`)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
import overlayStyle from "./deleteStyleOverlay.css?managed";
|
||||||
import textStyle from "./deleteStyleText.css?managed";
|
import textStyle from "./deleteStyleText.css?managed";
|
||||||
|
@ -122,7 +122,7 @@ export default definePlugin({
|
||||||
|
|
||||||
makeEdit(newMessage: any, oldMessage: any): any {
|
makeEdit(newMessage: any, oldMessage: any): any {
|
||||||
return {
|
return {
|
||||||
timestamp: moment?.call(newMessage.edited_timestamp),
|
timestamp: new Date(newMessage.edited_timestamp),
|
||||||
content: oldMessage.content
|
content: oldMessage.content
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -198,7 +198,7 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
// make the tag show the right text
|
// make the tag show the right text
|
||||||
{
|
{
|
||||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/,
|
||||||
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
|
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
|
||||||
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
|
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,11 +20,10 @@ import { Devs } from "@utils/constants";
|
||||||
import { isNonNullish } from "@utils/guards";
|
import { isNonNullish } from "@utils/guards";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common";
|
||||||
import { Channel, User } from "discord-types/general";
|
import { Channel, User } from "discord-types/general";
|
||||||
|
|
||||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||||
const AvatarUtils = findByPropsLazy("getChannelIconURL");
|
|
||||||
const UserUtils = findByPropsLazy("getGlobalName");
|
const UserUtils = findByPropsLazy("getGlobalName");
|
||||||
|
|
||||||
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
|
||||||
|
@ -71,7 +70,7 @@ export default definePlugin({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={AvatarUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
src={IconUtils.getChannelIconURL({ id: c.id, icon: c.icon, size: 32 })}
|
||||||
size="SIZE_40"
|
size="SIZE_40"
|
||||||
className={ProfileListClasses.listAvatar}
|
className={ProfileListClasses.listAvatar}
|
||||||
>
|
>
|
||||||
|
|
|
@ -104,6 +104,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
|
||||||
guildMember.nick || UserStore.getUser(guildMember.userId).username
|
guildMember.nick || UserStore.getUser(guildMember.userId).username
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
onDropDownClick={state => settings.store.defaultPermissionsDropdownState = !state}
|
||||||
defaultState={settings.store.defaultPermissionsDropdownState}
|
defaultState={settings.store.defaultPermissionsDropdownState}
|
||||||
buttons={[
|
buttons={[
|
||||||
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
|
(<Tooltip text={`Sorting by ${stns.permissionsSortOrder === PermissionsSortOrder.HighestRole ? "Highest Role" : "Lowest Role"}`}>
|
||||||
|
|
|
@ -24,7 +24,7 @@ const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PictureInPicture",
|
name: "PictureInPicture",
|
||||||
description: "Adds picture in picture to videos (next to the Download button)",
|
description: "Adds picture in picture to videos (next to the Download button)",
|
||||||
authors: [Devs.Lumap],
|
authors: [Devs.Nobody],
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,22 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { generateId, sendBotMessage } from "@api/Commands";
|
import { generateId, sendBotMessage } from "@api/Commands";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { StartAt } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
|
||||||
import { MessageAttachment } from "discord-types/general";
|
import { MessageAttachment } from "discord-types/general";
|
||||||
|
|
||||||
interface Props {
|
|
||||||
type: {
|
|
||||||
analyticsName: string;
|
|
||||||
isEmpty: boolean;
|
|
||||||
attachments: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const UploadStore = findByPropsLazy("getUploads");
|
const UploadStore = findByPropsLazy("getUploads");
|
||||||
|
|
||||||
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
||||||
|
@ -81,13 +73,11 @@ const getAttachments = async (channelId: string) =>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export function PreviewButton(chatBoxProps: Props) {
|
const PreviewButton: ChatBarButton = ({ isMainChat, isEmpty, type: { attachments } }) => {
|
||||||
const { isEmpty, attachments } = chatBoxProps.type;
|
|
||||||
|
|
||||||
const channelId = SelectedChannelStore.getChannelId();
|
const channelId = SelectedChannelStore.getChannelId();
|
||||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||||
|
|
||||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
||||||
const hasContent = !isEmpty && draft?.length > 0;
|
const hasContent = !isEmpty && draft?.length > 0;
|
||||||
|
@ -95,47 +85,47 @@ export function PreviewButton(chatBoxProps: Props) {
|
||||||
if (!hasContent && !hasAttachments) return null;
|
if (!hasContent && !hasAttachments) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text="Preview Message">
|
<ChatBarButton
|
||||||
{tooltipProps => (
|
tooltip="Preview Message"
|
||||||
<Button
|
onClick={async () =>
|
||||||
{...tooltipProps}
|
sendBotMessage(
|
||||||
onClick={async () =>
|
channelId,
|
||||||
sendBotMessage(
|
{
|
||||||
channelId,
|
content: getDraft(channelId),
|
||||||
{
|
author: UserStore.getCurrentUser(),
|
||||||
content: getDraft(channelId),
|
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
||||||
author: UserStore.getCurrentUser(),
|
}
|
||||||
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
)}
|
||||||
}
|
buttonProps={{
|
||||||
)}
|
style: {
|
||||||
size=""
|
translate: "0 2px"
|
||||||
look={ButtonLooks.BLANK}
|
}
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
}}
|
||||||
style={{ padding: "0 2px", height: "100%" }}
|
>
|
||||||
>
|
<svg
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
fill="currentColor"
|
||||||
<img width={24} height={24} src="https://discord.com/assets/4c5a77a89716352686f590a6f014770c.svg" />
|
fillRule="evenodd"
|
||||||
</div>
|
width="24"
|
||||||
</Button>
|
height="24"
|
||||||
)}
|
viewBox="0 0 24 24"
|
||||||
</Tooltip>
|
style={{ scale: "1.096", translate: "0 -1px" }}
|
||||||
|
>
|
||||||
|
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
|
||||||
|
</svg>
|
||||||
|
</ChatBarButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PreviewMessage",
|
name: "PreviewMessage",
|
||||||
description: "Lets you preview your message before sending it.",
|
description: "Lets you preview your message before sending it.",
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
patches: [
|
dependencies: ["ChatInputButtonAPI"],
|
||||||
{
|
// start early to ensure we're the first plugin to add our button
|
||||||
find: "ChannelTextAreaButtons",
|
// This makes the popping in less awkward
|
||||||
replacement: {
|
startAt: StartAt.Init,
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }),
|
start: () => addChatBarButton("previewMessage", PreviewButton),
|
||||||
|
stop: () => removeChatBarButton("previewMessage"),
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,16 +25,17 @@ function onClick() {
|
||||||
const channels: Array<any> = [];
|
const channels: Array<any> = [];
|
||||||
|
|
||||||
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
||||||
GuildChannelStore.getChannels(guild.id).SELECTABLE.forEach((c: { channel: { id: string; }; }) => {
|
GuildChannelStore.getChannels(guild.id).SELECTABLE
|
||||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
|
||||||
|
.forEach((c: { channel: { id: string; }; }) => {
|
||||||
|
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
||||||
|
|
||||||
channels.push({
|
channels.push({
|
||||||
channelId: c.channel.id,
|
channelId: c.channel.id,
|
||||||
// messageId: c.channel?.lastMessageId,
|
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
||||||
messageId: ReadStateStore.lastMessageId(c.channel.id),
|
readStateType: 0
|
||||||
readStateType: 0
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { filters, findBulk } from "@webpack";
|
import { filters, findBulk } from "@webpack";
|
||||||
import { Alerts, moment, Parser, Timestamp, useState } from "@webpack/common";
|
import { Alerts, Parser, Timestamp, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { Auth, getToken } from "../auth";
|
import { Auth, getToken } from "../auth";
|
||||||
import { Review, ReviewType } from "../entities";
|
import { Review, ReviewType } from "../entities";
|
||||||
|
@ -163,7 +163,7 @@ export default LazyComponent(() => {
|
||||||
|
|
||||||
{
|
{
|
||||||
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
!settings.store.hideTimestamps && review.type !== ReviewType.System && (
|
||||||
<Timestamp timestamp={moment(review.timestamp * 1000)} >
|
<Timestamp timestamp={new Date(review.timestamp * 1000)} >
|
||||||
{dateFormat.format(review.timestamp * 1000)}
|
{dateFormat.format(review.timestamp * 1000)}
|
||||||
</Timestamp>)
|
</Timestamp>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
@ -26,7 +27,7 @@ import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common";
|
import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
replaceMessageContents: {
|
replaceMessageContents: {
|
||||||
|
@ -122,25 +123,49 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatBarButton
|
||||||
|
tooltip="Insert Timestamp"
|
||||||
|
onClick={() => {
|
||||||
|
const key = openModal(props => (
|
||||||
|
<PickerModal
|
||||||
|
rootProps={props}
|
||||||
|
close={() => closeModal(key)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}}
|
||||||
|
buttonProps={{ "aria-haspopup": "dialog" }}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
style={{ scale: "1.2" }}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</svg>
|
||||||
|
</ChatBarButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SendTimestamps",
|
name: "SendTimestamps",
|
||||||
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!",
|
||||||
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11],
|
||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||||
|
|
||||||
settings: settings,
|
settings,
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "ChannelTextAreaButtons",
|
|
||||||
replacement: {
|
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
addChatBarButton("SendTimestamps", ChatBarIcon);
|
||||||
this.listener = addPreSendListener((_, msg) => {
|
this.listener = addPreSendListener((_, msg) => {
|
||||||
if (settings.store.replaceMessageContents) {
|
if (settings.store.replaceMessageContents) {
|
||||||
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime);
|
||||||
|
@ -149,56 +174,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
removeChatBarButton("SendTimestamps");
|
||||||
removePreSendListener(this.listener);
|
removePreSendListener(this.listener);
|
||||||
},
|
},
|
||||||
|
|
||||||
chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) {
|
|
||||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip text="Insert Timestamp">
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<Button
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-label="Insert Timestamp"
|
|
||||||
size=""
|
|
||||||
look={ButtonLooks.BLANK}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
|
||||||
onClick={() => {
|
|
||||||
const key = openModal(props => (
|
|
||||||
<PickerModal
|
|
||||||
rootProps={props}
|
|
||||||
close={() => closeModal(key)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}}
|
|
||||||
className={cl("button")}
|
|
||||||
>
|
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Tooltip >
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsAboutComponent() {
|
settingsAboutComponent() {
|
||||||
const samples = [
|
const samples = [
|
||||||
"12:00",
|
"12:00",
|
||||||
|
|
|
@ -42,10 +42,6 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-st-button {
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-st-button svg {
|
.vc-st-button svg {
|
||||||
transform: scale(1.1) translateY(1px);
|
transform: scale(1.1) translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,9 @@ import { classes } from "@utils/misc";
|
||||||
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
|
import { findByPropsLazy, findExportedComponentLazy } from "@webpack";
|
||||||
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common";
|
||||||
import { Guild, User } from "discord-types/general";
|
import { Guild, User } from "discord-types/general";
|
||||||
|
|
||||||
const IconUtils = findByPropsLazy("getGuildBannerURL");
|
|
||||||
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
|
const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper");
|
||||||
const FriendRow = findExportedComponentLazy("FriendRow");
|
const FriendRow = findExportedComponentLazy("FriendRow");
|
||||||
|
|
||||||
|
@ -50,7 +49,7 @@ const fetched = {
|
||||||
|
|
||||||
function renderTimestamp(timestamp: number) {
|
function renderTimestamp(timestamp: number) {
|
||||||
return (
|
return (
|
||||||
<Timestamp timestamp={moment(timestamp)} />
|
<Timestamp timestamp={new Date(timestamp)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,10 +64,7 @@ function GuildProfileModal({ guild }: GuildProps) {
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
|
||||||
|
|
||||||
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({
|
const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024");
|
||||||
id: guild.id,
|
|
||||||
banner: guild.banner
|
|
||||||
}, true).replace(/\?size=\d+$/, "?size=1024");
|
|
||||||
|
|
||||||
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
|
const iconUrl = guild.icon && IconUtils.getGuildIconURL({
|
||||||
id: guild.id,
|
id: guild.id,
|
||||||
|
@ -89,7 +85,7 @@ function GuildProfileModal({ guild }: GuildProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={cl("header")}>
|
<div className={cl("header")}>
|
||||||
{guild.icon
|
{iconUrl
|
||||||
? <img
|
? <img
|
||||||
src={iconUrl}
|
src={iconUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
@ -150,7 +146,7 @@ function Owner(guildId: string, owner: User) {
|
||||||
avatar: guildAvatar,
|
avatar: guildAvatar,
|
||||||
guildId,
|
guildId,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
}, true)
|
})
|
||||||
: IconUtils.getUserAvatarURL(owner, true);
|
: IconUtils.getUserAvatarURL(owner, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { formatDuration } from "@utils/text";
|
import { formatDuration } from "@utils/text";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack";
|
||||||
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
import type { Channel } from "discord-types/general";
|
import type { Channel } from "discord-types/general";
|
||||||
|
|
||||||
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
|
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
|
||||||
|
@ -120,7 +120,7 @@ const VideoQualityModesToNames = {
|
||||||
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg";
|
||||||
|
|
||||||
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
const [viewAllowedUsersAndRoles, setViewAllowedUsersAndRoles] = useState(settings.store.defaultAllowedUsersAndRolesDropdownState);
|
const { defaultAllowedUsersAndRolesDropdownState } = settings.use(["defaultAllowedUsersAndRolesDropdownState"]);
|
||||||
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
|
const [permissions, setPermissions] = useState<RoleOrUserPermission[]>([]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
{lastMessageId &&
|
{lastMessageId &&
|
||||||
<Text variant="text-md/normal">
|
<Text variant="text-md/normal">
|
||||||
Last {channel.isForumChannel() ? "post" : "message"} created:
|
Last {channel.isForumChannel() ? "post" : "message"} created:
|
||||||
<Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
<Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
{lastPinTimestamp &&
|
{lastPinTimestamp &&
|
||||||
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text>
|
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text>
|
||||||
}
|
}
|
||||||
{(rateLimitPerUser ?? 0) > 0 &&
|
{(rateLimitPerUser ?? 0) > 0 &&
|
||||||
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
|
<Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text>
|
||||||
|
@ -301,19 +301,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Text variant="text-lg/bold">Allowed users and roles:</Text>
|
<Text variant="text-lg/bold">Allowed users and roles:</Text>
|
||||||
<Tooltip text={viewAllowedUsersAndRoles ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
<Tooltip text={defaultAllowedUsersAndRolesDropdownState ? "Hide Allowed Users and Roles" : "View Allowed Users and Roles"}>
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<button
|
<button
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
|
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn"
|
||||||
onClick={() => setViewAllowedUsersAndRoles(v => !v)}
|
onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
transform={viewAllowedUsersAndRoles ? "scale(1 -1)" : "scale(1 1)"}
|
transform={defaultAllowedUsersAndRolesDropdownState ? "scale(1 -1)" : "scale(1 1)"}
|
||||||
>
|
>
|
||||||
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
<path fill="currentColor" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -321,7 +321,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{viewAllowedUsersAndRoles && <ChannelBeginHeader channel={channel} />}
|
{defaultAllowedUsersAndRolesDropdownState && <ChannelBeginHeader channel={channel} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,7 +29,7 @@ import type { Channel, Role } from "discord-types/general";
|
||||||
|
|
||||||
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
|
||||||
|
|
||||||
const ChannelListClasses = findByPropsLazy("channelEmoji", "unread", "icon");
|
const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
|
||||||
|
|
||||||
const enum ShowMode {
|
const enum ShowMode {
|
||||||
LockIcon,
|
LockIcon,
|
||||||
|
@ -162,7 +162,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Add the hidden eye icon if the channel is hidden
|
// Add the hidden eye icon if the channel is hidden
|
||||||
{
|
{
|
||||||
match: /\i\.children.+?:null(?<=,channel:(\i).+?)/,
|
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||||
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
|
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
|
||||||
},
|
},
|
||||||
// Make voice channels also appear as muted if they are muted
|
// Make voice channels also appear as muted if they are muted
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
* 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 { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common";
|
import { React, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
let lastState = false;
|
let lastState = false;
|
||||||
|
|
||||||
|
@ -41,19 +41,15 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function SilentMessageToggle(chatBoxProps: {
|
const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
type: {
|
const [enabled, setEnabled] = useState(lastState);
|
||||||
analyticsName: string;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const [enabled, setEnabled] = React.useState(lastState);
|
|
||||||
|
|
||||||
function setEnabledValue(value: boolean) {
|
function setEnabledValue(value: boolean) {
|
||||||
if (settings.store.persistState) lastState = value;
|
if (settings.store.persistState) lastState = value;
|
||||||
setEnabled(value);
|
setEnabled(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const listener: SendListener = (_, message) => {
|
const listener: SendListener = (_, message) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (settings.store.autoDisable) setEnabledValue(false);
|
if (settings.store.autoDisable) setEnabledValue(false);
|
||||||
|
@ -65,55 +61,39 @@ function SilentMessageToggle(chatBoxProps: {
|
||||||
return () => void removePreSendListener(listener);
|
return () => void removePreSendListener(listener);
|
||||||
}, [enabled]);
|
}, [enabled]);
|
||||||
|
|
||||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text={enabled ? "Disable Silent Message" : "Enable Silent Message"}>
|
<ChatBarButton
|
||||||
{tooltipProps => (
|
tooltip={enabled ? "Disable Silent Message" : "Enable Silent Message"}
|
||||||
<div style={{ display: "flex" }}>
|
onClick={() => setEnabledValue(!enabled)}
|
||||||
<Button
|
>
|
||||||
{...tooltipProps}
|
<svg
|
||||||
onClick={() => setEnabledValue(!enabled)}
|
width="24"
|
||||||
size=""
|
height="24"
|
||||||
look={ButtonLooks.BLANK}
|
viewBox="0 0 24 24"
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
style={{ scale: "1.2" }}
|
||||||
style={{ padding: "0 6px" }}
|
>
|
||||||
>
|
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
{!enabled && <>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
<mask id="_">
|
||||||
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||||
{!enabled && <>
|
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
||||||
<mask id="_">
|
</mask>
|
||||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
||||||
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
</>}
|
||||||
</mask>
|
</svg>
|
||||||
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
|
</ChatBarButton>
|
||||||
</>}
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SilentMessageToggle",
|
name: "SilentMessageToggle",
|
||||||
authors: [Devs.Nuckyz, Devs.CatNoir],
|
authors: [Devs.Nuckyz, Devs.CatNoir],
|
||||||
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
description: "Adds a button to the chat bar to toggle sending a silent message.",
|
||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "ChannelTextAreaButtons",
|
|
||||||
replacement: {
|
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
chatBarIcon: ErrorBoundary.wrap(SilentMessageToggle, { noop: true }),
|
start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle),
|
||||||
|
stop: () => removeChatBarButton("SilentMessageToggle")
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
* 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 { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common";
|
import { FluxDispatcher, React } from "@webpack/common";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showIcon: {
|
showIcon: {
|
||||||
|
@ -37,45 +37,32 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function SilentTypingToggle(chatBoxProps: {
|
const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => {
|
||||||
type: {
|
const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]);
|
||||||
analyticsName: string;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const { isEnabled } = settings.use(["isEnabled"]);
|
|
||||||
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
const toggle = () => settings.store.isEnabled = !settings.store.isEnabled;
|
||||||
|
|
||||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
if (!isMainChat || !showIcon) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}>
|
<ChatBarButton
|
||||||
{(tooltipProps: any) => (
|
tooltip={isEnabled ? "Disable Silent Typing" : "Enable Silent Typing"}
|
||||||
<div style={{ display: "flex" }}>
|
onClick={toggle}
|
||||||
<Button
|
>
|
||||||
{...tooltipProps}
|
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||||
onClick={toggle}
|
<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" />
|
||||||
size=""
|
{isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />}
|
||||||
look={ButtonLooks.BLANK}
|
</svg>
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
</ChatBarButton>
|
||||||
style={{ padding: "0 6px" }}
|
|
||||||
>
|
|
||||||
<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)" stroke-width="72" stroke-linecap="round" />}
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SilentTyping",
|
name: "SilentTyping",
|
||||||
authors: [Devs.Ven, Devs.Rini],
|
authors: [Devs.Ven, Devs.Rini],
|
||||||
description: "Hide that you are typing",
|
description: "Hide that you are typing",
|
||||||
|
dependencies: ["CommandsAPI", "ChatInputButtonAPI"],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
find: '.dispatch({type:"TYPING_START_LOCAL"',
|
||||||
|
@ -84,17 +71,8 @@ export default definePlugin({
|
||||||
replace: "startTyping:$self.startTyping,stop"
|
replace: "startTyping:$self.startTyping,stop"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: "ChannelTextAreaButtons",
|
|
||||||
predicate: () => settings.store.showIcon,
|
|
||||||
replacement: {
|
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
dependencies: ["CommandsAPI"],
|
|
||||||
settings,
|
|
||||||
commands: [{
|
commands: [{
|
||||||
name: "silenttype",
|
name: "silenttype",
|
||||||
description: "Toggle whether you're hiding that you're typing or not.",
|
description: "Toggle whether you're hiding that you're typing or not.",
|
||||||
|
@ -120,5 +98,6 @@ export default definePlugin({
|
||||||
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId });
|
||||||
},
|
},
|
||||||
|
|
||||||
chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }),
|
start: () => addChatBarButton("SilentTyping", SilentTypingToggle),
|
||||||
|
stop: () => removeChatBarButton("SilentTyping"),
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
* 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 { ChatBarButton } from "@api/ChatButtons";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Alerts, Forms } from "@webpack/common";
|
||||||
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { TranslateModal } from "./TranslateModal";
|
import { TranslateModal } from "./TranslateModal";
|
||||||
|
@ -37,42 +39,49 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?:
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { analyticsName: string; }; }; }) {
|
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||||
const { autoTranslate } = settings.use(["autoTranslate"]);
|
const { autoTranslate } = settings.use(["autoTranslate"]);
|
||||||
|
|
||||||
if (slateProps.type.analyticsName !== "normal")
|
if (!isMainChat) return null;
|
||||||
return null;
|
|
||||||
|
|
||||||
const toggle = () => settings.store.autoTranslate = !autoTranslate;
|
const toggle = () => {
|
||||||
|
const newState = !autoTranslate;
|
||||||
|
settings.store.autoTranslate = newState;
|
||||||
|
if (newState && settings.store.showAutoTranslateAlert !== false)
|
||||||
|
Alerts.show({
|
||||||
|
title: "Vencord Auto-Translate Enabled",
|
||||||
|
body: <>
|
||||||
|
<Forms.FormText>
|
||||||
|
You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent.
|
||||||
|
</Forms.FormText>
|
||||||
|
<Forms.FormText className={Margins.top16}>
|
||||||
|
If this was an accident, disable it again, or it will change your message content before sending.
|
||||||
|
</Forms.FormText>
|
||||||
|
</>,
|
||||||
|
cancelText: "Disable Auto-Translate",
|
||||||
|
confirmText: "Got it",
|
||||||
|
secondaryConfirmText: "Don't show again",
|
||||||
|
onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false,
|
||||||
|
onCancel: () => settings.store.autoTranslate = false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip text="Open Translate Modal">
|
<ChatBarButton
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
tooltip="Open Translate Modal"
|
||||||
<div style={{ display: "flex" }}>
|
onClick={e => {
|
||||||
<Button
|
if (e.shiftKey) return toggle();
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-label="Open Translate Modal"
|
|
||||||
size=""
|
|
||||||
look={ButtonLooks.BLANK}
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
innerClassName={ButtonWrapperClasses.button}
|
|
||||||
onClick={e => {
|
|
||||||
if (e.shiftKey) return toggle();
|
|
||||||
|
|
||||||
openModal(props => (
|
openModal(props => (
|
||||||
<TranslateModal rootProps={props} />
|
<TranslateModal rootProps={props} />
|
||||||
));
|
));
|
||||||
}}
|
}}
|
||||||
onContextMenu={() => toggle()}
|
onContextMenu={() => toggle()}
|
||||||
style={{ padding: "0 4px" }}
|
buttonProps={{
|
||||||
>
|
"aria-haspopup": "dialog"
|
||||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
}}
|
||||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate })} />
|
>
|
||||||
</div>
|
<TranslateIcon className={cl({ "auto-translate": autoTranslate, "chat-button": true })} />
|
||||||
</Button>
|
</ChatBarButton>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -18,11 +18,11 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore, Menu } from "@webpack/common";
|
import { ChannelStore, Menu } from "@webpack/common";
|
||||||
|
@ -55,25 +55,16 @@ export default definePlugin({
|
||||||
name: "Translate",
|
name: "Translate",
|
||||||
description: "Translate messages with Google Translate",
|
description: "Translate messages with Google Translate",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"],
|
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||||
settings,
|
settings,
|
||||||
// not used, just here in case some other plugin wants it or w/e
|
// not used, just here in case some other plugin wants it or w/e
|
||||||
translate,
|
translate,
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "ChannelTextAreaButtons",
|
|
||||||
replacement: {
|
|
||||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
|
||||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||||
|
|
||||||
addContextMenuPatch("message", messageCtxPatch);
|
addContextMenuPatch("message", messageCtxPatch);
|
||||||
|
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||||
|
|
||||||
addButton("vc-translate", message => {
|
addButton("vc-translate", message => {
|
||||||
if (!message.content) return null;
|
if (!message.content) return null;
|
||||||
|
@ -101,13 +92,8 @@ export default definePlugin({
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removePreSendListener(this.preSend);
|
||||||
removeContextMenuPatch("message", messageCtxPatch);
|
removeContextMenuPatch("message", messageCtxPatch);
|
||||||
|
removeChatBarButton("vc-translate");
|
||||||
removeButton("vc-translate");
|
removeButton("vc-translate");
|
||||||
removeAccessory("vc-translation");
|
removeAccessory("vc-translation");
|
||||||
},
|
},
|
||||||
|
|
||||||
chatBarIcon: (slateProps: any) => (
|
|
||||||
<ErrorBoundary noop>
|
|
||||||
<TranslateChatBarIcon slateProps={slateProps} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,4 +49,6 @@ export const settings = definePluginSettings({
|
||||||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
});
|
}).withPrivateSettings<{
|
||||||
|
showAutoTranslateAlert: boolean;
|
||||||
|
}>();
|
||||||
|
|
|
@ -35,3 +35,7 @@
|
||||||
.vc-trans-auto-translate {
|
.vc-trans-auto-translate {
|
||||||
color: var(--green-360);
|
color: var(--green-360);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-trans-chat-button {
|
||||||
|
scale: 1.085;
|
||||||
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "UNREAD_IMPORTANT:",
|
find: "UNREAD_IMPORTANT:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /channel:(\i).{0,100}?channelEmoji,.{0,250}?\.children.{0,50}?:null/,
|
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
|
||||||
replace: "$&,$self.TypingIndicator($1.id)"
|
replace: "$&,$self.TypingIndicator($1.id)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -96,7 +96,7 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
// above message box
|
// above message box
|
||||||
{
|
{
|
||||||
find: ".lastEditedByContainer",
|
find: ".popularApplicationCommandIds,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
|
match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/,
|
||||||
replace: "$self.patchPopout(arguments[0]),$&",
|
replace: "$self.patchPopout(arguments[0]),$&",
|
||||||
|
|
|
@ -22,11 +22,9 @@ import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { openImageModal } from "@utils/discord";
|
import { openImageModal } from "@utils/discord";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { GuildMemberStore, IconUtils, Menu } from "@webpack/common";
|
||||||
import { GuildMemberStore, Menu } from "@webpack/common";
|
|
||||||
import type { Channel, Guild, User } from "discord-types/general";
|
import type { Channel, Guild, User } from "discord-types/general";
|
||||||
|
|
||||||
const BannerStore = findByPropsLazy("getGuildBannerURL");
|
|
||||||
|
|
||||||
interface UserContextProps {
|
interface UserContextProps {
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
|
@ -91,19 +89,19 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-avatar"
|
id="view-avatar"
|
||||||
label="View Avatar"
|
label="View Avatar"
|
||||||
action={() => openImage(BannerStore.getUserAvatarURL(user, true))}
|
action={() => openImage(IconUtils.getUserAvatarURL(user, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
{memberAvatar && (
|
{memberAvatar && (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-server-avatar"
|
id="view-server-avatar"
|
||||||
label="View Server Avatar"
|
label="View Server Avatar"
|
||||||
action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({
|
action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
avatar: memberAvatar,
|
avatar: memberAvatar,
|
||||||
guildId,
|
guildId: guildId!,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
}, true))}
|
}))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -124,11 +122,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-icon"
|
id="view-icon"
|
||||||
label="View Icon"
|
label="View Icon"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(BannerStore.getGuildIconURL({
|
openImage(IconUtils.getGuildIconURL({
|
||||||
id,
|
id,
|
||||||
icon,
|
icon,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
}))
|
})!)
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
@ -138,10 +136,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-banner"
|
id="view-banner"
|
||||||
label="View Banner"
|
label="View Banner"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(BannerStore.getGuildBannerURL({
|
openImage(IconUtils.getGuildBannerURL(guild, true)!)
|
||||||
id,
|
|
||||||
banner,
|
|
||||||
}, true))
|
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -47,18 +47,23 @@ const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
||||||
const CDN_URL = "https://cdn.discordapp.com";
|
const CDN_URL = "cdn.discordapp.com";
|
||||||
|
|
||||||
function fixImageUrl(urlString: string, explodeWebp: boolean) {
|
function fixImageUrl(urlString: string) {
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
if (url.origin === CDN_URL) return urlString;
|
if (url.host === CDN_URL) return urlString;
|
||||||
if (url.origin === MEDIA_PROXY_URL) return CDN_URL + url.pathname;
|
|
||||||
|
|
||||||
url.searchParams.delete("width");
|
url.searchParams.delete("width");
|
||||||
url.searchParams.delete("height");
|
url.searchParams.delete("height");
|
||||||
url.searchParams.set("quality", "lossless");
|
|
||||||
if (explodeWebp && url.searchParams.get("format") === "webp")
|
if (url.origin === MEDIA_PROXY_URL) {
|
||||||
url.searchParams.set("format", "png");
|
url.host = CDN_URL;
|
||||||
|
url.searchParams.delete("size");
|
||||||
|
url.searchParams.delete("quality");
|
||||||
|
url.searchParams.delete("format");
|
||||||
|
} else {
|
||||||
|
url.searchParams.set("quality", "lossless");
|
||||||
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
@ -199,7 +204,7 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
async copyImage(url: string) {
|
async copyImage(url: string) {
|
||||||
url = fixImageUrl(url, true);
|
url = fixImageUrl(url);
|
||||||
|
|
||||||
let imageData = await fetch(url).then(r => r.blob());
|
let imageData = await fetch(url).then(r => r.blob());
|
||||||
if (imageData.type !== "image/png") {
|
if (imageData.type !== "image/png") {
|
||||||
|
@ -231,7 +236,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveImage(url: string) {
|
async saveImage(url: string) {
|
||||||
url = fixImageUrl(url, false);
|
url = fixImageUrl(url);
|
||||||
|
|
||||||
const data = await fetchImage(url);
|
const data = await fetchImage(url);
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
|
@ -42,6 +42,10 @@ export interface Dev {
|
||||||
* If you are fine with attribution but don't want the badge, add badge: false
|
* If you are fine with attribution but don't want the badge, add badge: false
|
||||||
*/
|
*/
|
||||||
export const Devs = /* #__PURE__*/ Object.freeze({
|
export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
|
Nobody: {
|
||||||
|
name: "Nobody",
|
||||||
|
id: 0n,
|
||||||
|
},
|
||||||
Ven: {
|
Ven: {
|
||||||
name: "Vendicated",
|
name: "Vendicated",
|
||||||
id: 343383572805058560n
|
id: 343383572805058560n
|
||||||
|
@ -359,10 +363,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "bb010g",
|
name: "bb010g",
|
||||||
id: 72791153467990016n,
|
id: 72791153467990016n,
|
||||||
},
|
},
|
||||||
Lumap: {
|
|
||||||
name: "lumap",
|
|
||||||
id: 635383782576357407n
|
|
||||||
},
|
|
||||||
Dolfies: {
|
Dolfies: {
|
||||||
name: "Dolfies",
|
name: "Dolfies",
|
||||||
id: 852892297661906993n,
|
id: 852892297661906993n,
|
||||||
|
|
3
src/webpack/common/types/components.d.ts
vendored
3
src/webpack/common/types/components.d.ts
vendored
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Moment } from "moment";
|
|
||||||
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
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";
|
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";
|
||||||
|
@ -154,7 +153,7 @@ export type Switch = ComponentType<PropsWithChildren<{
|
||||||
}>>;
|
}>>;
|
||||||
|
|
||||||
export type Timestamp = ComponentType<PropsWithChildren<{
|
export type Timestamp = ComponentType<PropsWithChildren<{
|
||||||
timestamp: Moment;
|
timestamp: Date;
|
||||||
isEdited?: boolean;
|
isEdited?: boolean;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
45
src/webpack/common/types/utils.d.ts
vendored
45
src/webpack/common/types/utils.d.ts
vendored
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Guild, GuildMember } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import type { FluxEvents } from "./fluxEvents";
|
import type { FluxEvents } from "./fluxEvents";
|
||||||
|
@ -182,3 +183,47 @@ export interface NavigationRouter {
|
||||||
getLastRouteChangeSource(): any;
|
getLastRouteChangeSource(): any;
|
||||||
getLastRouteChangeSourceLocationStack(): any;
|
getLastRouteChangeSourceLocationStack(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IconUtils {
|
||||||
|
getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string;
|
||||||
|
getDefaultAvatarURL(id: string, discriminator?: string): string;
|
||||||
|
getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
||||||
|
getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined;
|
||||||
|
|
||||||
|
getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null;
|
||||||
|
getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string;
|
||||||
|
getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined;
|
||||||
|
|
||||||
|
getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined;
|
||||||
|
getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null;
|
||||||
|
|
||||||
|
getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined;
|
||||||
|
getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string;
|
||||||
|
|
||||||
|
hasAnimatedGuildIcon(guild: Guild): boolean;
|
||||||
|
isAnimatedIconHash(hash: string): boolean;
|
||||||
|
|
||||||
|
getGuildSplashURL: any;
|
||||||
|
getGuildDiscoverySplashURL: any;
|
||||||
|
getGuildHomeHeaderURL: any;
|
||||||
|
getResourceChannelIconURL: any;
|
||||||
|
getNewMemberActionIconURL: any;
|
||||||
|
getGuildTemplateIconURL: any;
|
||||||
|
getApplicationIconURL: any;
|
||||||
|
getGameAssetURL: any;
|
||||||
|
getVideoFilterAssetURL: any;
|
||||||
|
|
||||||
|
getGuildMemberAvatarSource: any;
|
||||||
|
getUserAvatarSource: any;
|
||||||
|
getGuildSplashSource: any;
|
||||||
|
getGuildDiscoverySplashSource: any;
|
||||||
|
makeSource: any;
|
||||||
|
getGameAssetSource: any;
|
||||||
|
getGuildIconSource: any;
|
||||||
|
getGuildTemplateIconSource: any;
|
||||||
|
getGuildBannerSource: any;
|
||||||
|
getGuildHomeHeaderSource: any;
|
||||||
|
getChannelIconSource: any;
|
||||||
|
getApplicationIconSource: any;
|
||||||
|
getAnimatableSourceWithFallback: any;
|
||||||
|
}
|
||||||
|
|
|
@ -137,3 +137,5 @@ export const { persist: zustandPersist }: typeof import("zustand/middleware") =
|
||||||
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||||
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
||||||
export const InviteActions = findByPropsLazy("resolveInvite");
|
export const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
|
|
||||||
|
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
||||||
|
|
Loading…
Reference in a new issue