diff options
Diffstat (limited to 'src/plugins/viewIcons')
-rw-r--r-- | src/plugins/viewIcons/index.tsx | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx new file mode 100644 index 0000000..3bfe902 --- /dev/null +++ b/src/plugins/viewIcons/index.tsx @@ -0,0 +1,200 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/Settings"; +import { ImageIcon } from "@components/Icons"; +import { Devs } from "@utils/constants"; +import { openImageModal } from "@utils/discord"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { GuildMemberStore, Menu } from "@webpack/common"; +import type { Channel, Guild, User } from "discord-types/general"; + +const BannerStore = findByPropsLazy("getGuildBannerURL"); + +interface UserContextProps { + channel: Channel; + guildId?: string; + user: User; +} + +interface GuildContextProps { + guild?: Guild; +} + +const settings = definePluginSettings({ + format: { + type: OptionType.SELECT, + description: "Choose the image format to use for non animated images. Animated images will always use .gif", + options: [ + { + label: "webp", + value: "webp", + default: true + }, + { + label: "png", + value: "png", + }, + { + label: "jpg", + value: "jpg", + } + ] + }, + imgSize: { + type: OptionType.SELECT, + description: "The image size to use", + options: ["128", "256", "512", "1024", "2048", "4096"].map(n => ({ label: n, value: n, default: n === "1024" })) + } +}); + +function openImage(url: string) { + const format = url.startsWith("/") ? "png" : settings.store.format; + + const u = new URL(url, window.location.href); + u.searchParams.set("size", settings.store.imgSize); + u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${format}`); + url = u.toString(); + + u.searchParams.set("size", "4096"); + const originalUrl = u.toString(); + + openImageModal(url, { + original: originalUrl, + height: 256 + }); +} + +const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => { + const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null; + + children.splice(-1, 0, ( + <Menu.MenuGroup> + <Menu.MenuItem + id="view-avatar" + label="View Avatar" + action={() => openImage(BannerStore.getUserAvatarURL(user, true))} + icon={ImageIcon} + /> + {memberAvatar && ( + <Menu.MenuItem + id="view-server-avatar" + label="View Server Avatar" + action={() => openImage(BannerStore.getGuildMemberAvatarURLSimple({ + userId: user.id, + avatar: memberAvatar, + guildId, + canAnimate: true + }, true))} + icon={ImageIcon} + /> + )} + </Menu.MenuGroup> + )); +}; + +const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => () => { + if(!guild) return; + + const { id, icon, banner } = guild; + if (!banner && !icon) return; + + children.splice(-1, 0, ( + <Menu.MenuGroup> + {icon ? ( + <Menu.MenuItem + id="view-icon" + label="View Icon" + action={() => + openImage(BannerStore.getGuildIconURL({ + id, + icon, + canAnimate: true + })) + } + icon={ImageIcon} + /> + ) : null} + {banner ? ( + <Menu.MenuItem + id="view-banner" + label="View Banner" + action={() => + openImage(BannerStore.getGuildBannerURL({ + id, + banner, + }, true)) + } + icon={ImageIcon} + /> + ) : null} + </Menu.MenuGroup> + )); +}; + +export default definePlugin({ + name: "ViewIcons", + authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz], + description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu", + tags: ["ImageUtilities"], + + settings, + + openImage, + + start() { + addContextMenuPatch("user-context", UserContext); + addContextMenuPatch("guild-context", GuildContext); + }, + + stop() { + removeContextMenuPatch("user-context", UserContext); + removeContextMenuPatch("guild-context", GuildContext); + }, + + patches: [ + // Make pfps clickable + { + find: "onAddFriend:", + replacement: { + match: /\{src:(\i)(?=,avatarDecoration)/, + replace: "{src:$1,onClick:()=>$self.openImage($1)" + } + }, + // Make banners clickable + { + find: ".NITRO_BANNER,", + replacement: { + // style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl, + match: /style:\{(?=backgroundImage:(\i&&\i)\?"url\("\.concat\((\i),)/, + replace: + // onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0, + 'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,' + } + }, + { + find: "().avatarWrapperNonUserBot", + replacement: { + match: /(?<=avatarPositionPanel.+?)onClick:(\i\|\|\i)\?void 0(?<=,(\i)=\i\.avatarSrc.+?)/, + replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}" + } + } + ] +}); |