/* * Vencord, a Discord client mod * Copyright (c) 2023 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./styles.css"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { Tooltip } from "@webpack/common"; import type { Component } from "react"; interface Props { embed: { rawTitle: string; provider?: { name: string; }; thumbnail: { proxyURL: string; }; video: { url: string; }; dearrow: { enabled: boolean; oldTitle?: string; oldThumb?: string; }; }; } const enum ReplaceElements { ReplaceAllElements, ReplaceTitlesOnly, ReplaceThumbnailsOnly } const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/; async function embedDidMount(this: Component) { try { const { embed } = this.props; const { replaceElements, dearrowByDefault } = settings.store; if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return; const videoId = embedUrlRe.exec(embed.video.url)?.[1]; if (!videoId) return; const res = await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`); if (!res.ok) return; const { titles, thumbnails } = await res.json(); const hasTitle = titles[0]?.votes >= 0; const hasThumb = thumbnails[0]?.votes >= 0 && !thumbnails[0].original; if (!hasTitle && !hasThumb) return; embed.dearrow = { enabled: dearrowByDefault }; if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); embed.dearrow.oldTitle = dearrowByDefault ? embed.rawTitle : replacementTitle; if (dearrowByDefault) embed.rawTitle = replacementTitle; } if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL; if (dearrowByDefault) embed.thumbnail.proxyURL = replacementProxyURL; } this.forceUpdate(); } catch (err) { new Logger("Dearrow").error("Failed to dearrow embed", err); } } function DearrowButton({ component }: { component: Component; }) { const { embed } = component.props; if (!embed?.dearrow) return null; return ( {({ onMouseEnter, onMouseLeave }) => ( )} ); } const settings = definePluginSettings({ hideButton: { description: "Hides the Dearrow button from YouTube embeds", type: OptionType.BOOLEAN, default: false, restartNeeded: true }, replaceElements: { description: "Choose which elements of the embed will be replaced", type: OptionType.SELECT, restartNeeded: true, options: [ { label: "Everything (Titles & Thumbnails)", value: ReplaceElements.ReplaceAllElements, default: true }, { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, ], }, dearrowByDefault: { description: "Dearrow videos automatically", type: OptionType.BOOLEAN, default: true, restartNeeded: false } }); export default definePlugin({ name: "Dearrow", description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow", authors: [Devs.Ven], settings, embedDidMount, renderButton(component: Component) { return ( ); }, patches: [{ find: "this.renderInlineMediaEmbed", replacement: [ // patch componentDidMount to replace embed thumbnail and title { match: /render\(\)\{.{0,30}let\{embed:/, replace: "componentDidMount=$self.embedDidMount;$&" }, // add dearrow button { match: /children:\[(?=null!=\i\?(\i)\.renderSuppressButton)/, replace: "children:[$self.renderButton($1),", predicate: () => !settings.store.hideButton } ] }], });