aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/viewIcons/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/viewIcons/index.tsx')
-rw-r--r--src/plugins/viewIcons/index.tsx200
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)}"
+ }
+ }
+ ]
+});