From e08d49edacef63d1dabb9aa1a912935a8699626d Mon Sep 17 00:00:00 2001 From: V Date: Sat, 9 Sep 2023 19:17:50 +0200 Subject: [PATCH 01/10] New Plugin: Dearrow (#1723) --- src/plugins/dearrow/README.md | 5 ++ src/plugins/dearrow/index.tsx | 155 +++++++++++++++++++++++++++++++++ src/plugins/dearrow/styles.css | 12 +++ 3 files changed, 172 insertions(+) create mode 100644 src/plugins/dearrow/README.md create mode 100644 src/plugins/dearrow/index.tsx create mode 100644 src/plugins/dearrow/styles.css diff --git a/src/plugins/dearrow/README.md b/src/plugins/dearrow/README.md new file mode 100644 index 00000000..81a762c7 --- /dev/null +++ b/src/plugins/dearrow/README.md @@ -0,0 +1,5 @@ +# Dearrow + +Makes YouTube embed titles and thumbnails less sensationalist, powered by [Dearrow](https://dearrow.ajay.app/) + +https://github.com/Vendicated/Vencord/assets/45497981/7bf81108-102d-47c5-8ba5-357db4db1283 diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx new file mode 100644 index 00000000..7e50bcae --- /dev/null +++ b/src/plugins/dearrow/index.tsx @@ -0,0 +1,155 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; +import definePlugin 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 embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/; + +async function embedDidMount(this: Component) { + try { + const { embed } = this.props; + 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; + + if (!hasTitle && !hasThumb) return; + + embed.dearrow = { + enabled: true + }; + + if (titles[0]?.votes >= 0) { + embed.dearrow.oldTitle = embed.rawTitle; + embed.rawTitle = titles[0].title; + } + + if (thumbnails[0]?.votes >= 0) { + embed.dearrow.oldThumb = embed.thumbnail.proxyURL; + embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; + } + + this.forceUpdate(); + } catch (err) { + new Logger("Dearrow").error("Failed to dearrow embed", err); + } +} + +function renderButton(this: Component) { + const { embed } = this.props; + if (!embed?.dearrow) return null; + + return ( + + {({ onMouseEnter, onMouseLeave }) => ( + + )} + + ); +} + +export default definePlugin({ + name: "Dearrow", + description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow", + authors: [Devs.Ven], + + embedDidMount, + renderButton: ErrorBoundary.wrap(renderButton, { noop: true }), + + patches: [{ + find: "this.renderInlineMediaEmbed", + replacement: [ + // patch componentDidMount to replace embed thumbnail and title + { + match: /(\i).render=function.{0,50}\i\.embed/, + replace: "$1.componentDidMount=$self.embedDidMount,$&" + }, + + // add dearrow button + { + match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/, + replace: "children:[$self.renderButton.call(this)," + } + ] + }], +}); diff --git a/src/plugins/dearrow/styles.css b/src/plugins/dearrow/styles.css new file mode 100644 index 00000000..fc7e9e32 --- /dev/null +++ b/src/plugins/dearrow/styles.css @@ -0,0 +1,12 @@ +.vc-dearrow-toggle-off svg { + filter: grayscale(1); +} + +.vc-dearrow-toggle-on, .vc-dearrow-toggle-off { + all: unset; + display: inline; + cursor: pointer; + position: absolute; + top: 0.75rem; + right: 0.75rem; +} From 3a5b70d410d8c3e2c644732e5849ffcf88499d66 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 9 Sep 2023 19:49:11 +0200 Subject: [PATCH 02/10] Dearrow: Fix button --- src/plugins/dearrow/index.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 7e50bcae..f896531a 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -74,8 +74,8 @@ async function embedDidMount(this: Component) { } } -function renderButton(this: Component) { - const { embed } = this.props; +function renderButton(component: Component) { + const { embed } = component.props; if (!embed?.dearrow) return null; return ( @@ -97,7 +97,7 @@ function renderButton(this: Component) { embed.thumbnail.proxyURL = oldThumb; } - this.forceUpdate(); + component.forceUpdate(); }} > {/* Dearrow Icon, taken from https://dearrow.ajay.app/logo.svg (and optimised) */} @@ -134,7 +134,13 @@ export default definePlugin({ authors: [Devs.Ven], embedDidMount, - renderButton: ErrorBoundary.wrap(renderButton, { noop: true }), + renderButton(component: Component) { + return ( + + {renderButton(component)} + + ); + }, patches: [{ find: "this.renderInlineMediaEmbed", @@ -148,7 +154,7 @@ export default definePlugin({ // add dearrow button { match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/, - replace: "children:[$self.renderButton.call(this)," + replace: "children:[$self.renderButton(this)," } ] }], From 736477671542f0d1554d45faba8b5249d8e913c1 Mon Sep 17 00:00:00 2001 From: V Date: Sat, 9 Sep 2023 19:51:30 +0200 Subject: [PATCH 03/10] Dearrow: Fix button part 2 --- src/plugins/dearrow/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index f896531a..88d0dd6c 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -74,7 +74,7 @@ async function embedDidMount(this: Component) { } } -function renderButton(component: Component) { +function DearrowButton({ component }: { component: Component; }) { const { embed } = component.props; if (!embed?.dearrow) return null; @@ -137,7 +137,7 @@ export default definePlugin({ renderButton(component: Component) { return ( - {renderButton(component)} + ); }, From 09f65b401e53827035ed83d9363ddbd4a1d875c6 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 12 Sep 2023 04:28:21 -0300 Subject: [PATCH 04/10] Fix broken MessageLogger patches --- src/plugins/messageLogger/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 9c665607..64589281 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -326,14 +326,14 @@ export default definePlugin({ { // Attachment renderer // Module 96063 - find: "[\"className\",\"attachment\",\"inlineMedia\"", + find: "().removeAttachmentHoverButton", replacement: [ { match: /((\w)\.className,\w=\2\.attachment),/, replace: "$1,deleted=$2.attachment?.deleted," }, { - match: /\["className","attachment","inlineMedia".+?className:/, + match: /\["className","attachment".+?className:/, replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +" } ] From 4222c7fd9f0bc7b23f121b78d1f33a811a10f7ad Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 12 Sep 2023 05:01:18 -0300 Subject: [PATCH 05/10] Fix broken FakeNitro patch on canary --- src/plugins/fakeNitro.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/fakeNitro.ts b/src/plugins/fakeNitro.ts index d74a6c0c..669da191 100644 --- a/src/plugins/fakeNitro.ts +++ b/src/plugins/fakeNitro.ts @@ -212,15 +212,15 @@ export default definePlugin({ } }, { - find: "canStreamHighQuality:function", + find: "canUseHighVideoUploadQuality:function", predicate: () => settings.store.enableStreamQualityBypass, replacement: [ "canUseHighVideoUploadQuality", - "canStreamHighQuality", - "canStreamMidQuality" + // TODO: Remove the last two when they get removed from stable + "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)", ].map(func => { return { - match: new RegExp(`${func}:function\\(\\i\\){`), + match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`), replace: "$&return true;" }; }) From f23ddf4cae268f3e800935f0de890d41799c355f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 12 Sep 2023 05:10:44 -0300 Subject: [PATCH 06/10] oopsies --- src/plugins/fakeNitro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/fakeNitro.ts b/src/plugins/fakeNitro.ts index 669da191..f56239f5 100644 --- a/src/plugins/fakeNitro.ts +++ b/src/plugins/fakeNitro.ts @@ -220,7 +220,7 @@ export default definePlugin({ "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)", ].map(func => { return { - match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`), + match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"), replace: "$&return true;" }; }) From dd23f9802c6cd1c30256fe5916eaba742980691f Mon Sep 17 00:00:00 2001 From: lovenginx <144252537+lovenginx@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:04:50 -0700 Subject: [PATCH 07/10] InvisibleChat: fixup decryption modal (#1720) --- .../invisibleChat/components/DecryptionModal.tsx | 10 +++++----- src/plugins/invisibleChat/index.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/plugins/invisibleChat/components/DecryptionModal.tsx b/src/plugins/invisibleChat/components/DecryptionModal.tsx index a17239bc..b4bf6eb9 100644 --- a/src/plugins/invisibleChat/components/DecryptionModal.tsx +++ b/src/plugins/invisibleChat/components/DecryptionModal.tsx @@ -28,7 +28,7 @@ import { Button, Forms, React, TextInput } from "@webpack/common"; import { decrypt } from "../index"; export function DecModal(props: any) { - const secret: string = props?.message?.content; + const encryptedMessage: string = props?.message?.content; const [password, setPassword] = React.useState("password"); return ( @@ -38,9 +38,9 @@ export function DecModal(props: any) { - Secret - - Password + Message with Encryption + + Password { - const toSend = decrypt(secret, password, true); + const toSend = decrypt(encryptedMessage, password, true); if (!toSend || !props?.message) return; // @ts-expect-error Vencord.Plugins.plugins.InvisibleChat.buildEmbed(props?.message, toSend); diff --git a/src/plugins/invisibleChat/index.tsx b/src/plugins/invisibleChat/index.tsx index 43b48f10..58fccb9c 100644 --- a/src/plugins/invisibleChat/index.tsx +++ b/src/plugins/invisibleChat/index.tsx @@ -225,8 +225,8 @@ export function encrypt(secret: string, password: string, cover: string): string return steggo.hide(secret + "\u200b", password, cover); } -export function decrypt(secret: string, password: string, removeIndicator: boolean): string { - const decrypted = steggo.reveal(secret, password); +export function decrypt(encrypted: string, password: string, removeIndicator: boolean): string { + const decrypted = steggo.reveal(encrypted, password); return removeIndicator ? decrypted.replace("\u200b", "") : decrypted; } From a95311ef2cb4f22bd554de97a7b635b3e9d4de06 Mon Sep 17 00:00:00 2001 From: Archer Date: Tue, 12 Sep 2023 17:11:25 -0400 Subject: [PATCH 08/10] lastfm: Add setting for artist name and song title only (#1726) Co-authored-by: V --- src/plugins/lastfm.tsx | 16 +++++++++++++++- src/utils/constants.ts | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/plugins/lastfm.tsx b/src/plugins/lastfm.tsx index 66be06aa..7a42f8f7 100644 --- a/src/plugins/lastfm.tsx +++ b/src/plugins/lastfm.tsx @@ -76,6 +76,8 @@ const enum NameFormat { StatusName = "status-name", ArtistFirst = "artist-first", SongFirst = "song-first", + ArtistOnly = "artist", + SongOnly = "song" } const applicationId = "1108588077900898414"; @@ -143,6 +145,14 @@ const settings = definePluginSettings({ { label: "Use format 'song - artist'", value: NameFormat.SongFirst + }, + { + label: "Use artist name only", + value: NameFormat.ArtistOnly + }, + { + label: "Use song name only", + value: NameFormat.SongOnly } ], }, @@ -171,7 +181,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "LastFMRichPresence", description: "Little plugin for Last.fm rich presence", - authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip], + authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu], settingsAboutComponent: () => ( <> @@ -298,6 +308,10 @@ export default definePlugin({ return trackData.artist + " - " + trackData.name; case NameFormat.SongFirst: return trackData.name + " - " + trackData.artist; + case NameFormat.ArtistOnly: + return trackData.artist; + case NameFormat.SongOnly: + return trackData.name; default: return settings.store.statusName; } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 424c9aae..cd6a7a22 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -370,6 +370,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ blahajZip: { name: "blahaj.zip", id: 683954422241427471n, + }, + archeruwu: { + name: "archer_uwu", + id: 160068695383736320n } } satisfies Record); From cf7c4d63b6a8f11a4b8d96d39e4b5ca0c0cf4bc3 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Wed, 13 Sep 2023 04:11:53 +0700 Subject: [PATCH 09/10] pictureInPicture: don't show PiP button on normal file (#1725) --- src/plugins/pictureInPicture.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/pictureInPicture.tsx b/src/plugins/pictureInPicture.tsx index 7a9e7903..d10d42ff 100644 --- a/src/plugins/pictureInPicture.tsx +++ b/src/plugins/pictureInPicture.tsx @@ -29,8 +29,8 @@ export default definePlugin({ { find: ".onRemoveAttachment,", replacement: { - match: /\.nonMediaAttachment.{0,10}children:\[(\i),/, - replace: "$&$1&&$self.renderPiPButton()," + match: /\.nonMediaAttachment,!(\i).{0,7}children:\[(\i),/, + replace: "$&$1&&$2&&$self.renderPiPButton()," }, }, ], From a73d09a2f0d2adc7ff56e6f6004cd6ec50e202e9 Mon Sep 17 00:00:00 2001 From: Syncx <47534062+Syncxv@users.noreply.github.com> Date: Wed, 13 Sep 2023 07:14:17 +1000 Subject: [PATCH 10/10] PreviewMessage: Add attachments (& misc changes) (#1715) --- src/plugins/favEmojiFirst/README.md | 6 ++ .../index.ts} | 0 src/plugins/favGifSearch/README.md | 5 ++ .../index.tsx} | 2 +- src/plugins/imageZoom/README.md | 6 ++ src/plugins/imageZoom/index.tsx | 9 +++ src/plugins/previewMessage/README.md | 5 ++ .../index.tsx} | 69 +++++++++++++++++-- src/plugins/searchReply/README.md | 5 ++ .../index.tsx} | 0 src/webpack/common/types/menu.d.ts | 9 ++- 11 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/plugins/favEmojiFirst/README.md rename src/plugins/{favEmojiFirst.ts => favEmojiFirst/index.ts} (100%) create mode 100644 src/plugins/favGifSearch/README.md rename src/plugins/{favGifSearch.tsx => favGifSearch/index.tsx} (99%) create mode 100644 src/plugins/imageZoom/README.md create mode 100644 src/plugins/previewMessage/README.md rename src/plugins/{previewMessage.tsx => previewMessage/index.tsx} (56%) create mode 100644 src/plugins/searchReply/README.md rename src/plugins/{searchReply.tsx => searchReply/index.tsx} (100%) diff --git a/src/plugins/favEmojiFirst/README.md b/src/plugins/favEmojiFirst/README.md new file mode 100644 index 00000000..dc844802 --- /dev/null +++ b/src/plugins/favEmojiFirst/README.md @@ -0,0 +1,6 @@ +# FavoriteEmojiFirst + +Puts your favorite emoji first in the emoji autocomplete. + +![FavEmojis](https://i.imgur.com/mEFCoZG.png) +![Example](https://i.imgur.com/wY3Tc43.png) diff --git a/src/plugins/favEmojiFirst.ts b/src/plugins/favEmojiFirst/index.ts similarity index 100% rename from src/plugins/favEmojiFirst.ts rename to src/plugins/favEmojiFirst/index.ts diff --git a/src/plugins/favGifSearch/README.md b/src/plugins/favGifSearch/README.md new file mode 100644 index 00000000..c0768859 --- /dev/null +++ b/src/plugins/favGifSearch/README.md @@ -0,0 +1,5 @@ +# FavoriteGifSearch + +Adds a search bar to favorite gifs. + +![Screenshot](https://i.imgur.com/Bcgb7PD.png) diff --git a/src/plugins/favGifSearch.tsx b/src/plugins/favGifSearch/index.tsx similarity index 99% rename from src/plugins/favGifSearch.tsx rename to src/plugins/favGifSearch/index.tsx index db575a03..d10c5153 100644 --- a/src/plugins/favGifSearch.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -87,7 +87,7 @@ export const settings = definePluginSettings({ export default definePlugin({ name: "FavoriteGifSearch", authors: [Devs.Aria], - description: "Adds a search bar for favorite gifs", + description: "Adds a search bar to favorite gifs.", patches: [ { diff --git a/src/plugins/imageZoom/README.md b/src/plugins/imageZoom/README.md new file mode 100644 index 00000000..8e3b7efd --- /dev/null +++ b/src/plugins/imageZoom/README.md @@ -0,0 +1,6 @@ +# ImageZoom + +Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size + +![Example](https://i.imgur.com/VJdo4aq.png) +![ContextMenu](https://i.imgur.com/0oaRM2s.png) diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 71540f2b..cca0db02 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -99,6 +99,15 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => { ContextMenu.close(); }} /> + { + settings.store.nearestNeighbour = !settings.store.nearestNeighbour; + ContextMenu.close(); + }} + /> . */ -import { sendBotMessage } from "@api/Commands"; +import { generateId, sendBotMessage } from "@api/Commands"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { MessageAttachment } from "discord-types/general"; interface Props { type: { analyticsName: string; + isEmpty: boolean; + attachments: boolean; }; } +const UploadStore = findByPropsLazy("getUploads"); + const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage); + +const getImageBox = (url: string): Promise<{ width: number, height: number; } | null> => + new Promise(res => { + const img = new Image(); + img.onload = () => + res({ width: img.width, height: img.height }); + + img.onerror = () => + res(null); + + img.src = url; + }); + + +const getAttachments = async (channelId: string) => + await Promise.all( + UploadStore.getUploads(channelId, DraftType.ChannelMessage) + .map(async (upload: any) => { + const { isImage, filename, spoiler, item: { file } } = upload; + const url = URL.createObjectURL(file); + const attachment: MessageAttachment = { + id: generateId(), + filename: spoiler ? "SPOILER_" + filename : filename, + // weird eh? if i give it the normal content type the preview doenst work + content_type: undefined, + size: await upload.getSize(), + spoiler, + // discord adds query params to the url, so we need to add a hash to prevent that + url: url + "#", + proxy_url: url + "#", + }; + + if (isImage) { + const box = await getImageBox(url); + if (!box) return attachment; + + attachment.width = box.width; + attachment.height = box.height; + } + + return attachment; + }) + ); + + export function PreviewButton(chatBoxProps: Props) { + const { isEmpty, attachments } = chatBoxProps.type; + const channelId = SelectedChannelStore.getChannelId(); const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); + if (chatBoxProps.type.analyticsName !== "normal") return null; - if (!draft) return null; + + const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0; + const hasContent = !isEmpty && draft?.length > 0; + + if (!hasContent && !hasAttachments) return null; return ( {tooltipProps => (