Quoter: Fix & Shrink + Remove Old Finds
This commit is contained in:
parent
1f062fee89
commit
262336f436
21 changed files with 161 additions and 300 deletions
|
|
@ -40,13 +40,12 @@ import { openWarningModal } from "./PluginModal";
|
|||
import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards";
|
||||
|
||||
// Avoid circular dependency
|
||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("plugins"));
|
||||
const { stopPlugin } = proxyLazy(() => require("plugins"));
|
||||
|
||||
export const cl = classNameFactory("vc-plugins-");
|
||||
export const logger = new Logger("PluginSettings", "#a6d189");
|
||||
|
||||
const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error");
|
||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||
|
||||
function showErrorToast(message: string) {
|
||||
Toasts.show({
|
||||
|
|
|
|||
|
|
@ -22,17 +22,6 @@ import definePlugin, { OptionType } from "@utils/types";
|
|||
import { VoiceState } from "@vencord/discord-types";
|
||||
import { ChannelStore, FluxDispatcher, GuildMemberStore, Toasts, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
|
||||
interface ChannelState {
|
||||
userId: string;
|
||||
channelId: string;
|
||||
deaf: boolean;
|
||||
mute: boolean;
|
||||
stream: boolean;
|
||||
selfDeaf: boolean;
|
||||
selfMute: boolean;
|
||||
selfStream: boolean;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
port: {
|
||||
type: OptionType.NUMBER,
|
||||
|
|
|
|||
|
|
@ -178,12 +178,10 @@ function shouldHideBadgeOnUserProfiles(): boolean {
|
|||
function shouldHideQuestPopup(quest: Quest | null): boolean {
|
||||
const {
|
||||
disableQuestsPopupAboveAccountPanel,
|
||||
disableQuestsEverything,
|
||||
triggerQuestsRerender
|
||||
disableQuestsEverything
|
||||
} = settings.use([
|
||||
"disableQuestsPopupAboveAccountPanel",
|
||||
"disableQuestsEverything",
|
||||
"triggerQuestsRerender"
|
||||
"disableQuestsEverything"
|
||||
]);
|
||||
|
||||
const noProgress = !quest?.userStatus?.progress || Object.keys(quest?.userStatus?.progress || {}).length === 0;
|
||||
|
|
@ -251,16 +249,12 @@ function QuestTileContextMenu(children: React.ReactNode[], props: { quest: any;
|
|||
|
||||
export function getQuestTileClasses(originalClasses: string, quest: Quest, color: number | null | undefined, gradient: string | undefined): string {
|
||||
const {
|
||||
ignoredQuestIDs,
|
||||
ignoredQuestProfile,
|
||||
restyleQuestsUnclaimed,
|
||||
restyleQuestsClaimed,
|
||||
restyleQuestsIgnored,
|
||||
restyleQuestsExpired,
|
||||
restyleQuestsGradient
|
||||
} = settings.use([
|
||||
"ignoredQuestIDs",
|
||||
"ignoredQuestProfile",
|
||||
"restyleQuestsUnclaimed",
|
||||
"restyleQuestsClaimed",
|
||||
"restyleQuestsIgnored",
|
||||
|
|
@ -326,7 +320,7 @@ export function getQuestTileClasses(originalClasses: string, quest: Quest, color
|
|||
}
|
||||
|
||||
function makeDesktopCompatible(quests: Quest[]): void {
|
||||
const { makeMobileQuestsDesktopCompatible, triggerQuestsRerender } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]);
|
||||
const { makeMobileQuestsDesktopCompatible } = settings.use(["makeMobileQuestsDesktopCompatible", "triggerQuestsRerender"]);
|
||||
|
||||
if (makeMobileQuestsDesktopCompatible) {
|
||||
quests.forEach(quest => {
|
||||
|
|
@ -358,27 +352,17 @@ function makeDesktopCompatible(quests: Quest[]): void {
|
|||
|
||||
function sortQuests(quests: Quest[], skip?: boolean): Quest[] {
|
||||
const {
|
||||
ignoredQuestIDs,
|
||||
ignoredQuestProfile,
|
||||
reorderQuests,
|
||||
unclaimedSubsort,
|
||||
claimedSubsort,
|
||||
ignoredSubsort,
|
||||
expiredSubsort,
|
||||
completeVideoQuestsInBackground,
|
||||
completeGameQuestsInBackground,
|
||||
triggerQuestsRerender
|
||||
expiredSubsort
|
||||
} = settings.use([
|
||||
"ignoredQuestIDs",
|
||||
"ignoredQuestProfile",
|
||||
"reorderQuests",
|
||||
"unclaimedSubsort",
|
||||
"claimedSubsort",
|
||||
"ignoredSubsort",
|
||||
"expiredSubsort",
|
||||
"completeVideoQuestsInBackground",
|
||||
"completeGameQuestsInBackground",
|
||||
"triggerQuestsRerender"
|
||||
"expiredSubsort"
|
||||
]);
|
||||
|
||||
makeDesktopCompatible(quests);
|
||||
|
|
@ -468,16 +452,6 @@ function sortQuests(quests: Quest[], skip?: boolean): Quest[] {
|
|||
}
|
||||
|
||||
export function getQuestTileStyle(quest: Quest | null): Record<string, string> {
|
||||
const {
|
||||
restyleQuests,
|
||||
ignoredQuestIDs,
|
||||
ignoredQuestProfile
|
||||
} = settings.use([
|
||||
"restyleQuests",
|
||||
"ignoredQuestIDs",
|
||||
"ignoredQuestProfile"
|
||||
]);
|
||||
|
||||
const style: Record<string, string> = {};
|
||||
let themeColor: RGB | null = null;
|
||||
|
||||
|
|
@ -801,7 +775,6 @@ function disguiseHomeButton(location: string): boolean {
|
|||
}
|
||||
|
||||
function useQuestRerender(): number {
|
||||
const { triggerQuestsRerender } = settings.use(["triggerQuestsRerender"]);
|
||||
const [renderTrigger, setRenderTrigger] = useState(0);
|
||||
useEffect(() => addRerenderCallback(() => setRenderTrigger(prev => prev + 1)), []);
|
||||
return renderTrigger;
|
||||
|
|
@ -818,12 +791,10 @@ function getLastFilterChoices(): { group: string; filter: string; }[] | null {
|
|||
}
|
||||
|
||||
function setLastSortChoice(sort: string): void {
|
||||
const { rememberQuestPageFilters } = settings.use(["rememberQuestPageFilters"]);
|
||||
settings.store.lastQuestPageSort = sort;
|
||||
}
|
||||
|
||||
function setLastFilterChoices(filters: { group: string; filter: string; }[]): void {
|
||||
const { rememberQuestPageFilters } = settings.use(["rememberQuestPageFilters"]);
|
||||
if (!filters || !Object.keys(filters).length || !Object.values(filters).every(f => f.group && f.filter)) { return; }
|
||||
settings.store.lastQuestPageFilters = JSON.parse(JSON.stringify(filters)).reduce((acc, item) => ({ ...acc, [item.filter]: item }), {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,7 +364,6 @@ function validateQuestButtonSetting() {
|
|||
|
||||
function validateDisableQuestSetting() {
|
||||
const {
|
||||
disableQuestsEverything,
|
||||
disableQuestsDiscoveryTab,
|
||||
disableQuestsFetchingQuests,
|
||||
disableQuestsPopupAboveAccountPanel,
|
||||
|
|
@ -372,7 +371,6 @@ function validateDisableQuestSetting() {
|
|||
disableQuestsGiftInventoryRelocationNotice,
|
||||
disableFriendsListActiveNowPromotion
|
||||
} = settings.use([
|
||||
"disableQuestsEverything",
|
||||
"disableQuestsDiscoveryTab",
|
||||
"disableQuestsFetchingQuests",
|
||||
"disableQuestsPopupAboveAccountPanel",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { findGroupChildrenByChildId } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
|
|
@ -20,44 +20,28 @@ enum ImageStyle {
|
|||
inspirational
|
||||
}
|
||||
|
||||
const messagePatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||
recentmessage = message;
|
||||
if (!message.content) return;
|
||||
|
||||
const buttonElement =
|
||||
<Menu.MenuItem
|
||||
id="vc-quote"
|
||||
label="Quote"
|
||||
icon={QuoteIcon}
|
||||
action={async () => {
|
||||
openModal(props => <QuoteModal {...props} />);
|
||||
}}
|
||||
/>;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
if (!group) {
|
||||
children.push(buttonElement);
|
||||
return;
|
||||
}
|
||||
|
||||
group.splice(
|
||||
group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement
|
||||
);
|
||||
};
|
||||
|
||||
let recentmessage: Message;
|
||||
let grayscale;
|
||||
let setStyle: ImageStyle = ImageStyle.inspirational;
|
||||
let customMessage: string = "";
|
||||
|
||||
enum userIDOptions {
|
||||
displayName,
|
||||
userName,
|
||||
userId
|
||||
}
|
||||
|
||||
interface QuoteImageOptions {
|
||||
avatarUrl: string;
|
||||
quoteOld: string;
|
||||
grayScale: boolean;
|
||||
imageStyle: ImageStyle;
|
||||
author: {
|
||||
username: string;
|
||||
globalName?: string;
|
||||
id: string;
|
||||
};
|
||||
userIdentifier: userIDOptions;
|
||||
}
|
||||
|
||||
|
||||
const settings = definePluginSettings({
|
||||
userIdentifier:
|
||||
{
|
||||
userIdentifier: {
|
||||
type: OptionType.SELECT,
|
||||
description: "What the author's name should be displayed as",
|
||||
options: [
|
||||
|
|
@ -71,14 +55,28 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "Quoter",
|
||||
description: "Adds the ability to create an inspirational quote image from a message",
|
||||
authors: [Devs.Samwich],
|
||||
authors: [Devs.Samwich, Devs.thororen],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messagePatch
|
||||
},
|
||||
settings
|
||||
"message": (children, { message }) => {
|
||||
if (!message.content) return;
|
||||
const buttonElement = (
|
||||
<Menu.MenuItem
|
||||
id="vc-quote"
|
||||
label="Quote"
|
||||
icon={QuoteIcon}
|
||||
action={() => openModal(props => <QuoteModal message={message} {...props} />)}
|
||||
/>
|
||||
);
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
if (!group) children.push(buttonElement);
|
||||
else group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function sizeUpgrade(url) {
|
||||
function sizeUpgrade(url: string) {
|
||||
const u = new URL(url);
|
||||
u.searchParams.set("size", "512");
|
||||
return u.toString();
|
||||
|
|
@ -87,68 +85,49 @@ function sizeUpgrade(url) {
|
|||
const preparingSentence: string[] = [];
|
||||
const lines: string[] = [];
|
||||
|
||||
async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise<Blob> {
|
||||
async function createQuoteImage(options: QuoteImageOptions): Promise<Blob> {
|
||||
const { avatarUrl, quoteOld, grayScale, imageStyle, author, userIdentifier } = options;
|
||||
|
||||
const quote = FixUpQuote(quoteOld);
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) throw new Error("Cant get 2d rendering context :(");
|
||||
|
||||
if (!ctx) {
|
||||
throw new Error("Cant get 2d rendering context :(");
|
||||
}
|
||||
|
||||
let name: string = "";
|
||||
|
||||
switch (settings.store.userIdentifier) {
|
||||
let name = "";
|
||||
switch (userIdentifier) {
|
||||
case userIDOptions.displayName:
|
||||
// @ts-ignore
|
||||
const meow = recentmessage.author.globalName;
|
||||
if (meow) {
|
||||
name = meow;
|
||||
}
|
||||
else {
|
||||
name = recentmessage.author.username;
|
||||
}
|
||||
name = author.globalName || author.username;
|
||||
break;
|
||||
case userIDOptions.userName:
|
||||
name = recentmessage.author.username;
|
||||
name = author.username;
|
||||
break;
|
||||
case userIDOptions.userId:
|
||||
name = recentmessage.author.id;
|
||||
name = author.id;
|
||||
break;
|
||||
default:
|
||||
name = "MAN WTF HAPPENED";
|
||||
break;
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
switch (setStyle) {
|
||||
switch (imageStyle) {
|
||||
case ImageStyle.inspirational:
|
||||
|
||||
const cardWidth = 1200;
|
||||
const cardHeight = 600;
|
||||
|
||||
canvas.width = cardWidth;
|
||||
canvas.height = cardHeight;
|
||||
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
||||
|
||||
const avatarBlob = await fetchImageAsBlob(avatarUrl);
|
||||
const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/refs/heads/main/icons/quoter/quoter.png");
|
||||
const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/main/icons/quoter/quoter.png");
|
||||
|
||||
const avatar = new Image();
|
||||
const fade = new Image();
|
||||
|
||||
const avatarPromise = new Promise<void>(resolve => {
|
||||
avatar.onload = () => resolve();
|
||||
avatar.src = URL.createObjectURL(avatarBlob);
|
||||
});
|
||||
|
||||
const fadePromise = new Promise<void>(resolve => {
|
||||
fade.onload = () => resolve();
|
||||
fade.src = URL.createObjectURL(fadeBlob);
|
||||
});
|
||||
|
||||
await Promise.all([avatarPromise, fadePromise]);
|
||||
await Promise.all([
|
||||
new Promise<void>(resolve => { avatar.onload = () => resolve(); avatar.src = URL.createObjectURL(avatarBlob); }),
|
||||
new Promise<void>(resolve => { fade.onload = () => resolve(); fade.src = URL.createObjectURL(fadeBlob); })
|
||||
]);
|
||||
|
||||
ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight);
|
||||
|
||||
|
|
@ -164,42 +143,69 @@ async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale:
|
|||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "italic 20px Georgia";
|
||||
const quoteWidth = cardWidth / 2 - 50;
|
||||
const quoteX = ((cardWidth - cardHeight));
|
||||
const quoteX = cardWidth - cardHeight;
|
||||
const quoteY = cardHeight / 2 - 10;
|
||||
wrapText(ctx, `"${quote}"`, quoteX, quoteY, quoteWidth, 20, preparingSentence, lines);
|
||||
|
||||
const wrappedTextHeight = lines.length * 25;
|
||||
|
||||
ctx.font = "bold 16px Georgia";
|
||||
const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30;
|
||||
const authorNameX = cardHeight * 1.5 - ctx.measureText(`- ${name}`).width / 2 - 30;
|
||||
const authorNameY = quoteY + wrappedTextHeight + 30;
|
||||
|
||||
ctx.fillText(`- ${name}`, authorNameX, authorNameY);
|
||||
|
||||
preparingSentence.length = 0;
|
||||
lines.length = 0;
|
||||
|
||||
return await canvasToBlob(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
function registerStyleChange(style) {
|
||||
setStyle = style;
|
||||
GeneratePreview();
|
||||
function generateFileNamePreview(message: string) {
|
||||
const words = message.split(" ");
|
||||
return words.length >= 6 ? words.slice(0, 6).join(" ") : words.join(" ");
|
||||
}
|
||||
|
||||
function QuoteModal(props: ModalProps) {
|
||||
function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) {
|
||||
const [gray, setGray] = useState(true);
|
||||
useEffect(() => {
|
||||
grayscale = gray;
|
||||
GeneratePreview();
|
||||
}, [gray]);
|
||||
const [style, setStyle] = useState(ImageStyle.inspirational);
|
||||
const [quoteImage, setQuoteImage] = useState<Blob | null>(null);
|
||||
const { userIdentifier } = settings.store;
|
||||
const safeContent = message.content ? message.content : "";
|
||||
|
||||
const safeContent = recentmessage && recentmessage.content ? recentmessage.content : "";
|
||||
const generateImage = async () => {
|
||||
const image = await createQuoteImage({
|
||||
avatarUrl: sizeUpgrade(message.author.getAvatarURL()),
|
||||
quoteOld: safeContent,
|
||||
grayScale: gray,
|
||||
imageStyle: style,
|
||||
author: message.author,
|
||||
userIdentifier
|
||||
});
|
||||
setQuoteImage(image);
|
||||
document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image));
|
||||
};
|
||||
|
||||
const [custom, setCustom] = useState(safeContent);
|
||||
useEffect(() => {
|
||||
customMessage = custom;
|
||||
GeneratePreview();
|
||||
}, [custom]);
|
||||
useEffect(() => { generateImage(); }, [gray, style, safeContent]);
|
||||
|
||||
const Export = () => {
|
||||
if (!quoteImage) return;
|
||||
const link = document.createElement("a");
|
||||
const preview = generateFileNamePreview(safeContent);
|
||||
link.href = URL.createObjectURL(quoteImage);
|
||||
link.download = `${preview} - ${message.author.username}.png`;
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
|
||||
const SendInChat = () => {
|
||||
if (!quoteImage) return;
|
||||
const preview = generateFileNamePreview(safeContent);
|
||||
const file = new File([quoteImage], `${preview} - ${message.author.username}.png`, { type: "image/png" });
|
||||
// @ts-expect-error typing issue
|
||||
UploadHandler.promptToUpload([file], getCurrentChannel(), 0);
|
||||
props.onClose?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
|
|
@ -210,59 +216,27 @@ function QuoteModal(props: ModalProps) {
|
|||
<ModalCloseButton onClick={props.onClose} />
|
||||
</ModalHeader>
|
||||
<ModalContent scrollbarType="none">
|
||||
<img alt="" src="" id={"quoterPreview"} style={{ borderRadius: "20px", width: "100%" }}></img>
|
||||
<br></br><br></br>
|
||||
<img alt="" src="" id="quoterPreview" style={{ borderRadius: "20px", width: "100%" }} />
|
||||
<br /><br />
|
||||
<br /><br />
|
||||
<Switch value={gray} onChange={setGray}>Grayscale</Switch>
|
||||
<Select look={1}
|
||||
options={Object.keys(ImageStyle).filter(key => isNaN(parseInt(key, 10))).map(key => ({
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
value: ImageStyle[key as keyof typeof ImageStyle]
|
||||
}))}
|
||||
select={v => registerStyleChange(v)} isSelected={v => v === setStyle}
|
||||
serialize={v => v}></Select>
|
||||
<Select
|
||||
look={1}
|
||||
options={Object.keys(ImageStyle)
|
||||
.filter(key => isNaN(parseInt(key, 10)))
|
||||
.map(key => ({
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
value: ImageStyle[key as keyof typeof ImageStyle]
|
||||
}))}
|
||||
select={v => setStyle(v)}
|
||||
isSelected={v => v === style}
|
||||
serialize={v => v}
|
||||
/>
|
||||
<br />
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => Export()} style={{ display: "inline-block", marginRight: "5px" }}>Export</Button>
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => SendInChat(props.onClose)} style={{ display: "inline-block" }}>Send</Button>
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={async () => await Export()} style={{ display: "inline-block", marginRight: "5px" }}>Export</Button>
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={async () => await SendInChat()} style={{ display: "inline-block" }}>Send</Button>
|
||||
</ModalContent>
|
||||
<br></br>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
async function SendInChat(onClose) {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
const preview = generateFileNamePreview(recentmessage.content);
|
||||
const imageName = `${preview} - ${recentmessage.author.username}`;
|
||||
const file = new File([image], `${imageName}.png`, { type: "image/png" });
|
||||
// @ts-expect-error typing issue
|
||||
UploadHandler.promptToUpload([file], getCurrentChannel(), 0);
|
||||
onClose();
|
||||
}
|
||||
|
||||
async function Export() {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(image);
|
||||
const preview = generateFileNamePreview(recentmessage.content);
|
||||
|
||||
const imageName = `${preview} - ${recentmessage.author.username}`;
|
||||
link.download = `${imageName}.png`;
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
async function GeneratePreview() {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image));
|
||||
}
|
||||
|
||||
function generateFileNamePreview(message) {
|
||||
const words = message.split(" ");
|
||||
let preview;
|
||||
if (words.length >= 6) {
|
||||
preview = words.slice(0, 6).join(" ");
|
||||
} else {
|
||||
preview = words.join(" ");
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,61 +7,39 @@
|
|||
import { UserStore } from "@webpack/common";
|
||||
|
||||
export function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
||||
return new Promise<Blob>(resolve => {
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
canvas.toBlob(blob => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
} else {
|
||||
throw new Error("Failed to create Blob");
|
||||
}
|
||||
blob ? resolve(blob) : reject(new Error("Failed to create Blob"));
|
||||
}, "image/png");
|
||||
});
|
||||
}
|
||||
|
||||
export function wrapText(context: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number, preparingSentence: string[], lines: string[]) {
|
||||
const words = text.split(" ");
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const workSentence = preparingSentence.join(" ") + " " + words[i];
|
||||
|
||||
if (context.measureText(workSentence).width > maxWidth) {
|
||||
export function wrapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number, preparingSentence: string[], lines: string[]) {
|
||||
text.split(" ").forEach(word => {
|
||||
const workSentence = preparingSentence.join(" ") + " " + word;
|
||||
if (ctx.measureText(workSentence).width > maxWidth) {
|
||||
lines.push(preparingSentence.join(" "));
|
||||
preparingSentence = [words[i]];
|
||||
preparingSentence = [word];
|
||||
} else {
|
||||
preparingSentence.push(words[i]);
|
||||
preparingSentence.push(word);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lines.push(preparingSentence.join(" "));
|
||||
|
||||
lines.forEach(element => {
|
||||
const lineWidth = context.measureText(element).width;
|
||||
const xOffset = (maxWidth - lineWidth) / 2;
|
||||
|
||||
lines.forEach(line => {
|
||||
const xOffset = (maxWidth - ctx.measureText(line).width) / 2;
|
||||
y += lineHeight;
|
||||
context.fillText(element, x + xOffset, y);
|
||||
ctx.fillText(line, x + xOffset, y);
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchImageAsBlob(url: string): Promise<Blob> {
|
||||
if (!url) throw new Error("Invalid URL: URL is empty or undefined");
|
||||
try {
|
||||
if (!url) throw new Error("Invalid URL: URL is empty or undefined");
|
||||
|
||||
const response = await fetch(url, {
|
||||
mode: "cors",
|
||||
cache: "no-cache",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"Accept": "image/*"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
return blob;
|
||||
const response = await fetch(`https://corsproxy.io/?url=${encodeURIComponent(url)}`);
|
||||
if (!response.ok) throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
||||
return await response.blob();
|
||||
} catch (error) {
|
||||
console.error("Error fetching image:", error);
|
||||
return new Blob([
|
||||
|
|
@ -76,31 +54,18 @@ export async function fetchImageAsBlob(url: string): Promise<Blob> {
|
|||
}
|
||||
}
|
||||
|
||||
export function FixUpQuote(quote) {
|
||||
const emojiRegex = /<a?:(\w+):(\d+)>/g;
|
||||
quote = quote.replace(emojiRegex, "");
|
||||
|
||||
const mentionRegex = /<@(.*)>/;
|
||||
let result = quote;
|
||||
|
||||
export function FixUpQuote(quote: string) {
|
||||
try {
|
||||
const matches = mentionRegex.exec(quote);
|
||||
if (matches) {
|
||||
matches.forEach(match => {
|
||||
quote = quote.replace(/<a?:(\w+):(\d+)>/g, "");
|
||||
const mentionMatch = /<@(.*?)>/.exec(quote);
|
||||
if (mentionMatch) {
|
||||
mentionMatch.forEach(match => {
|
||||
try {
|
||||
const userId = match.replace("<@", "").replace(">", "");
|
||||
const user = UserStore.getUser(userId);
|
||||
if (user && user.username) {
|
||||
result = result.replace(match, `@${user.username}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error processing mention:", err);
|
||||
}
|
||||
const user = UserStore.getUser(match.replace(/[<@>]/g, ""));
|
||||
if (user?.username) quote = quote.replace(match, `@${user.username}`);
|
||||
} catch { }
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error in FixUpQuote:", err);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch { }
|
||||
return quote;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export default definePlugin({
|
|||
}
|
||||
],
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
VOICE_STATE_UPDATES() {
|
||||
const currentUserId = UserStore.getCurrentUser().id;
|
||||
const myChannelId = VoiceStateStore.getVoiceStateForUser(currentUserId)?.channelId;
|
||||
if (!myChannelId || !settings.store.leaveEmpty) return;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default definePlugin({
|
|||
if (!repliedMessage || repliedMessage.author.id !== user.id) return;
|
||||
|
||||
const whitelist = settings.store.replyPingWhitelist.split(",").map(id => id.trim());
|
||||
const isWhitelisted = settings.store.replyPingWhitelist.includes(message.author.id);
|
||||
const isWhitelisted = whitelist.includes(message.author.id);
|
||||
|
||||
if (isWhitelisted || settings.store.alwaysPingOnReply) {
|
||||
if (!message.mentions.some(mention => mention.id === user.id)) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
|
@ -103,8 +102,6 @@ interface SFMR {
|
|||
item: Item;
|
||||
}
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
const applicationId = "1325126169179197500";
|
||||
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { ApplicationAssetUtils, FluxDispatcher } from "@webpack/common";
|
|||
|
||||
import { BanchoStatusEnum, GameState, Modes, TosuApi, UserLoginStatus } from "./type";
|
||||
|
||||
const socketId = "tosu";
|
||||
const OSU_APP_ID = "367827983903490050";
|
||||
const OSU_LARGE_IMAGE = "373344233077211136";
|
||||
const OSU_STARDARD_SMALL_IMAGE = "373370493127884800";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
|
@ -44,8 +43,6 @@ const enum NameFormat {
|
|||
AlbumName = "album"
|
||||
}
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
const applicationId = "1108588077900898414";
|
||||
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ export default definePlugin({
|
|||
oldMsg === newMsg,
|
||||
);
|
||||
|
||||
const { showEditDiffs, inlineEdits } = settings.use(["showEditDiffs", "inlineEdits"]);
|
||||
const { inlineEdits } = settings.use(["inlineEdits"]);
|
||||
|
||||
return inlineEdits && (
|
||||
<React.Fragment key={disabledDiffMessages.has(messageId) ? `diff-off-${messageId}` : `diff-on-${messageId}`}>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { Devs } from "@utils/constants";
|
|||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const EMOTE = "<:luna:1035316192220553236>";
|
||||
const DATA_KEY = "MessageTags_TAGS";
|
||||
const MessageTagsMarker = Symbol("MessageTags");
|
||||
|
||||
interface Tag {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
filterStream(channelStream: [ChannelStreamGroupProps | ChannelStreamMessageProps | ChannelStreamDividerProps]) {
|
||||
const { alsoHideIgnoredUsers, disableNotifications, hideBlockedUserReplies, allowAutoModMessages, defaultHideUsers, overrideUsers } = settings.use();
|
||||
const { alsoHideIgnoredUsers } = settings.use();
|
||||
|
||||
const newChannelStream = channelStream.map(item => {
|
||||
if (item.type === "MESSAGE_GROUP_BLOCKED" || (item.type === "MESSAGE_GROUP_IGNORED" && alsoHideIgnoredUsers)) {
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@ export interface Category {
|
|||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
const CATEGORY_BASE_KEY = "PinDMsCategories-";
|
||||
const CATEGORY_MIGRATED_PINDMS_KEY = "PinDMsMigratedPinDMs";
|
||||
const CATEGORY_MIGRATED_KEY = "PinDMsMigratedOldCategories";
|
||||
const OLD_CATEGORY_KEY = "BetterPinDMsCategories-";
|
||||
|
||||
let forceUpdateDms: (() => void) | undefined = undefined;
|
||||
export let currentUserCategories: Category[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ const enum ChannelFlags {
|
|||
|
||||
|
||||
const ChatScrollClasses = findByPropsLazy("auto", "managedReactiveScroller");
|
||||
const ChatClasses = findByPropsLazy("chat", "content", "noChat", "chatContent");
|
||||
const ChannelBeginHeader = findComponentByCodeLazy("#{intl::ROLE_REQUIRED_SINGLE_USER_MESSAGE}");
|
||||
const TagComponent = findComponentByCodeLazy("#{intl::FORUM_TAG_A11Y_FILTER_BY_TAG}");
|
||||
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ function renderUsername(
|
|||
const isVoice = type === "voiceChannel";
|
||||
|
||||
const config = hookless ? settings.store : settings.use();
|
||||
const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, hideDefaultAtSign, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, animateGradients, includedNames, friendNameColor, nicknameColor, displayNameColor, usernameColor, triggerNameRerender } = config;
|
||||
const { messages, replies, mentions, memberList, profilePopout, reactions, discriminators, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreGradients, animateGradients, includedNames, friendNameColor, nicknameColor, displayNameColor, usernameColor } = config;
|
||||
|
||||
const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : false);
|
||||
const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d";
|
||||
|
|
@ -921,22 +921,22 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
flux: {
|
||||
RELATIONSHIP_UPDATE(data) {
|
||||
RELATIONSHIP_UPDATE() {
|
||||
// Allows rerendering when changing friend names.
|
||||
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
|
||||
},
|
||||
|
||||
RUNNING_STREAMER_TOOLS_CHANGE(data) {
|
||||
RUNNING_STREAMER_TOOLS_CHANGE() {
|
||||
// Allows rerendering when toggling streamer mode.
|
||||
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
|
||||
},
|
||||
|
||||
ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED(data) {
|
||||
ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED() {
|
||||
// Allows rerendering when toggling reduced motion.
|
||||
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
|
||||
},
|
||||
|
||||
ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION(data) {
|
||||
ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION() {
|
||||
// Allows rerendering when toggling reduced motion.
|
||||
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,21 +121,11 @@ const SilentTypingChatToggle: ChatBarButtonFactory = ({ channel, type }) => {
|
|||
const {
|
||||
enabledGlobally,
|
||||
chatIcon,
|
||||
defaultHidden,
|
||||
enabledLocations,
|
||||
disabledLocations,
|
||||
chatIconLeftClickAction,
|
||||
chatIconMiddleClickAction,
|
||||
chatIconRightClickAction,
|
||||
defaultHidden
|
||||
} = settings.use([
|
||||
"enabledGlobally",
|
||||
"chatIcon",
|
||||
"defaultHidden",
|
||||
"enabledLocations",
|
||||
"disabledLocations",
|
||||
"chatIconLeftClickAction",
|
||||
"chatIconMiddleClickAction",
|
||||
"chatIconRightClickAction",
|
||||
"defaultHidden"
|
||||
]);
|
||||
|
||||
const validChat = ["normal", "sidebar"].some(x => type.analyticsName === x);
|
||||
|
|
@ -238,16 +228,12 @@ const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => {
|
|||
chatIcon,
|
||||
chatContextMenu,
|
||||
enabledGlobally,
|
||||
defaultHidden,
|
||||
hideChatBoxTypingIndicators,
|
||||
hideMembersListTypingIndicators
|
||||
defaultHidden
|
||||
} = settings.use([
|
||||
"chatIcon",
|
||||
"chatContextMenu",
|
||||
"enabledGlobally",
|
||||
"defaultHidden",
|
||||
"hideChatBoxTypingIndicators",
|
||||
"hideMembersListTypingIndicators"
|
||||
"defaultHidden"
|
||||
]);
|
||||
|
||||
if (!chatContextMenu) return;
|
||||
|
|
|
|||
|
|
@ -8,17 +8,13 @@ import { classNameFactory } from "@api/Styles";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { classes } from "@utils/misc";
|
||||
import { Channel } from "@vencord/discord-types";
|
||||
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const cl = classNameFactory("vc-uvs-");
|
||||
|
||||
const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChannel");
|
||||
const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
|
||||
useChannelName: filters.byCode("()=>null==")
|
||||
});
|
||||
|
||||
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
|
||||
|
||||
type IconProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
|
||||
|
|
|
|||
|
|
@ -24,10 +24,8 @@ import { Queue } from "@utils/Queue";
|
|||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common";
|
||||
|
||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||
let Scroll: any = null;
|
||||
const queue = new Queue();
|
||||
let reactions: Record<string, ReactionCacheEntry>;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ interface NotificationObject {
|
|||
}
|
||||
|
||||
const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1");
|
||||
const logger = new Logger("XSOverlay");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
webSocketPort: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue