diff --git a/src/equicordplugins/quoter/index.tsx b/src/equicordplugins/quoter/index.tsx index 2597374c1..42cb7f6a6 100644 --- a/src/equicordplugins/quoter/index.tsx +++ b/src/equicordplugins/quoter/index.tsx @@ -8,57 +8,69 @@ import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { BaseText } from "@components/BaseText"; import { FormSwitch } from "@components/FormSwitch"; -import { Devs } from "@utils/constants"; +import { Devs, EquicordDevs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { Message } from "@vencord/discord-types"; -import { Button, Menu, Select, UploadHandler, useEffect, useState } from "@webpack/common"; +import { Button, Menu, TextInput, UploadHandler, useEffect, useState } from "@webpack/common"; import { QuoteIcon } from "./components"; -import { canvasToBlob, fetchImageAsBlob, FixUpQuote, wrapText } from "./utils"; - -enum ImageStyle { - inspirational -} - -enum userIDOptions { - displayName, - userName, - userId -} - -interface QuoteImageOptions { - avatarUrl: string; - quoteOld: string; - grayScale: boolean; - imageStyle: ImageStyle; - author: { - username: string; - globalName?: string; - id: string; - }; - userIdentifier: userIDOptions; -} - +import { createQuoteImage, ensureFontLoaded, generateFileNamePreview, QuoteFont, resetFontLoading, sizeUpgrade } from "./utils"; const settings = definePluginSettings({ - userIdentifier: { + quoteFont: { type: OptionType.SELECT, - description: "What the author's name should be displayed as", + description: "Font for quote text (author/username always use M PLUS Rounded 1c)", options: [ - { label: "Display Name", value: userIDOptions.displayName, default: true }, - { label: "Username", value: userIDOptions.userName }, - { label: "User ID", value: userIDOptions.userId } + { label: "M PLUS Rounded 1c", value: QuoteFont.MPlusRounded, default: true }, + { label: "Open Sans", value: QuoteFont.OpenSans }, + { label: "Momo Signature", value: QuoteFont.MomoSignature }, + { label: "Lora", value: QuoteFont.Lora }, + { label: "Merriweather", value: QuoteFont.Merriweather } ] + }, + watermark: { + type: OptionType.STRING, + description: "Custom watermark text (max 32 characters)", + default: "Made with Equicord" + }, + grayscale: { + type: OptionType.BOOLEAN, + description: "Enable grayscale by default", + default: true, + hidden: true + }, + showWatermark: { + type: OptionType.BOOLEAN, + description: "Show watermark by default", + default: false, + hidden: true + }, + saveAsGif: { + type: OptionType.BOOLEAN, + description: "Save as GIF by default", + default: false, + hidden: true } }); export default definePlugin({ name: "Quoter", description: "Adds the ability to create an inspirational quote image from a message", - authors: [Devs.Samwich, Devs.thororen], + authors: [Devs.Samwich, Devs.thororen, EquicordDevs.neoarz], settings, + + async start() { + await ensureFontLoaded(); + }, + + stop() { + const style = document.getElementById("quoter-font-style"); + if (style) style.remove(); + resetFontLoading(); + }, + contextMenus: { "message": (children, { message }) => { if (!message.content) return; @@ -78,124 +90,51 @@ export default definePlugin({ } }); -function sizeUpgrade(url: string) { - const u = new URL(url); - u.searchParams.set("size", "512"); - return u.toString(); -} - -const preparingSentence: string[] = []; -const lines: string[] = []; - -async function createQuoteImage(options: QuoteImageOptions): Promise { - 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 :("); - - let name = ""; - switch (userIdentifier) { - case userIDOptions.displayName: - name = author.globalName || author.username; - break; - case userIDOptions.userName: - name = author.username; - break; - case userIDOptions.userId: - name = author.id; - break; - default: - name = "Unknown"; - } - - switch (imageStyle) { - case ImageStyle.inspirational: - const cardWidth = 1200; - const cardHeight = 600; - canvas.width = cardWidth; - canvas.height = cardHeight; - - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, cardWidth, cardHeight); - - const avatarBlob = await fetchImageAsBlob(avatarUrl); - const fadeBlob = await fetchImageAsBlob("https://raw.githubusercontent.com/Equicord/Equibored/main/icons/quoter/quoter.png"); - - const avatar = new Image(); - const fade = new Image(); - - await Promise.all([ - new Promise(resolve => { avatar.onload = () => resolve(); avatar.src = URL.createObjectURL(avatarBlob); }), - new Promise(resolve => { fade.onload = () => resolve(); fade.src = URL.createObjectURL(fadeBlob); }) - ]); - - ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight); - - if (grayScale) { - ctx.globalCompositeOperation = "saturation"; - ctx.fillStyle = "#fff"; - ctx.fillRect(0, 0, cardWidth, cardHeight); - ctx.globalCompositeOperation = "source-over"; - } - - ctx.drawImage(fade, cardHeight - 400, 0, 400, cardHeight); - - ctx.fillStyle = "#fff"; - ctx.font = "italic 20px Georgia"; - const quoteWidth = cardWidth / 2 - 50; - 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 authorNameY = quoteY + wrappedTextHeight + 30; - ctx.fillText(`- ${name}`, authorNameX, authorNameY); - - preparingSentence.length = 0; - lines.length = 0; - - return await canvasToBlob(canvas); - } -} - -function generateFileNamePreview(message: string) { - const words = message.split(" "); - return words.length >= 6 ? words.slice(0, 6).join(" ") : words.join(" "); -} - function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) { - const [gray, setGray] = useState(true); - const [style, setStyle] = useState(ImageStyle.inspirational); + const [gray, setGray] = useState(settings.store.grayscale); + const [showWatermark, setShowWatermark] = useState(settings.store.showWatermark); + const [saveAsGif, setSaveAsGif] = useState(settings.store.saveAsGif); + const [watermarkText, setWatermarkText] = useState(settings.store.watermark); const [quoteImage, setQuoteImage] = useState(null); - const { userIdentifier } = settings.store; + const { quoteFont } = settings.store; const safeContent = message.content ? message.content : ""; + useEffect(() => { + settings.store.grayscale = gray; + }, [gray]); + + useEffect(() => { + settings.store.showWatermark = showWatermark; + }, [showWatermark]); + + useEffect(() => { + settings.store.saveAsGif = saveAsGif; + }, [saveAsGif]); + const generateImage = async () => { const image = await createQuoteImage({ avatarUrl: sizeUpgrade(message.author.getAvatarURL()), quoteOld: safeContent, grayScale: gray, - imageStyle: style, author: message.author, - userIdentifier + watermark: watermarkText, + showWatermark, + saveAsGif, + quoteFont }); setQuoteImage(image); document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image)); }; - useEffect(() => { generateImage(); }, [gray, style, safeContent]); + useEffect(() => { generateImage(); }, [gray, showWatermark, saveAsGif, safeContent, watermarkText, quoteFont]); const Export = () => { if (!quoteImage) return; const link = document.createElement("a"); const preview = generateFileNamePreview(safeContent); + const extension = saveAsGif ? "gif" : "png"; link.href = URL.createObjectURL(quoteImage); - link.download = `${preview} - ${message.author.username}.png`; + link.download = `${preview} - ${message.author.username}.${extension}`; link.click(); link.remove(); }; @@ -203,7 +142,9 @@ function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) { const SendInChat = () => { if (!quoteImage) return; const preview = generateFileNamePreview(safeContent); - const file = new File([quoteImage], `${preview} - ${message.author.username}.png`, { type: "image/png" }); + const extension = saveAsGif ? "gif" : "png"; + const mimeType = saveAsGif ? "image/gif" : "image/png"; + const file = new File([quoteImage], `${preview} - ${message.author.username}.${extension}`, { type: mimeType }); // @ts-expect-error typing issue UploadHandler.promptToUpload([file], getCurrentChannel(), 0); props.onClose?.(); @@ -222,21 +163,36 @@ function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) {



-