Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HideMedia: Add support for automatic hiding of media via defining list of user ids or domains #3192

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
196 changes: 179 additions & 17 deletions src/plugins/hideAttachments/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ import "./styles.css";

import { get, set } from "@api/DataStore";
import { updateMessage } from "@api/MessageUpdater";
import { migratePluginSettings } from "@api/Settings";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore } from "@webpack/common";
import { MessageSnapshot } from "@webpack/types";
import { Embed } from "discord-types/general";

import { ILoadMessagesSuccessPayload, IMessage, IMessageCreatePayload, IMessageUpdatePayload } from "./types";
import { isStringEmpty } from "./utils";

const KEY = "HideAttachments_HiddenIds";

Expand All @@ -41,10 +45,159 @@ const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids);

migratePluginSettings("HideMedia", "HideAttachments");

/**
* Toggle attachment/embed hiding
*/
const toggleHide = async (channelId: string, messageId: string): Promise<void> => {
const ids = await getHiddenMessages();
if (!ids.delete(messageId))
ids.add(messageId);

await saveHiddenMessages(ids);
updateMessage(channelId, messageId);
};

/**
* Determine if the message should be blocked according to user ID filter
* @param {Message} payload The message to be checked
* @param {string[]} userFilters List of user IDs to be checked
* @returns {boolean}
*/
const shouldHideByUserIdFilter = (payload: IMessage, userFilters: Set<string>): boolean => {
if (userFilters.has(payload.author.id)) {
// Check if it's a forwarded messages with embeds/attachments
if (Array.isArray(payload.message_snapshots)) {
const hasMedia = payload.message_snapshots.some(snapshot => {
return snapshot.message.attachments.length > 0 || snapshot.message.embeds.length > 0;
});
if (hasMedia) {
return true;
}
}

// Otherwise, just check if the message contain embeds/attachments
return payload.attachments.length > 0 || payload.embeds.length > 0;
}

return false;
};

/**
* Checks if the embed should be hidden
* @param {Embed[]} embeds List of embeds
* @param {string[]} domainList List of domains
*/
const shouldHideEmbed = (embeds: Embed[], domainList: string[]): boolean => {
for (const embed of embeds) {
if (!embed.url) {
continue;
}

for (const domain of domainList) {
const host = URL.parse(embed.url)?.hostname ?? "";
if (host.indexOf(domain) >= 0) {
return true;
}
}
}

return false;
};

/**
* Determine if the message should be blocked according to domain list filter
* @param {Message} payload The message to be checked
* @param {string[]} domainList List of domains to be checked
* @returns {boolean}
*/
const shouldHideByDomainListFilter = (payload: IMessage, domainList: string[]): boolean => {
if (payload.embeds.length <= 0) {
return false;
}

if (shouldHideEmbed(payload.embeds, domainList)) {
return true;
}

// Check embeds from the forwarded messages
const hasReference = payload.message_reference && Array.isArray(payload.message_snapshots);
if (!hasReference) {
return false;
}

for (const snapshot of payload.message_snapshots!) {
if (shouldHideEmbed(snapshot.message.embeds, domainList)) {
return true;
}
}

return false;
};

/**
* Checks and hides the attachment/embed
* @param {Message} message The message to check
* @param {object} store The configuration values
*/
const checkAndHide = async (message: IMessage, store: typeof settings.store): Promise<void> => {
if (!store.enableAutoHideAttachments) {
return;
}

if (hiddenMessages.has(message.id)) {
return;
}

const userFilters = isStringEmpty(store.filterUserList)
? []
: store.filterUserList.split(",");
if (shouldHideByUserIdFilter(message, new Set(userFilters))) {
await toggleHide(message.channel_id, message.id);
return;
}

const domainFilters = isStringEmpty(store.filterDomainList)
? []
: store.filterDomainList.split(",");
if (shouldHideByDomainListFilter(message, domainFilters)) {
await toggleHide(message.channel_id, message.id);
return;
}

// Forwarded messages
const hasReference = message.message_reference && Array.isArray(message.message_snapshots);
if (hasReference) {
for (const snapshot of message.message_snapshots!) {
if (shouldHideByDomainListFilter(snapshot.message, domainFilters)) {
await toggleHide(message.channel_id, message.id);
return;
}
}
}
};

const settings = definePluginSettings({
enableAutoHideAttachments: {
type: OptionType.BOOLEAN,
description: "Enable auto hide attachments",
default: false,
},
filterUserList: {
type: OptionType.STRING,
description: "Comma separated list of User IDs to automatically hide their attachments/embeds. (Requires auto hide to be ON)",
default: "",
},
filterDomainList: {
type: OptionType.STRING,
description: "Comma separated list of domains to automatically hide their embeds. (Requires auto hide to be ON)",
default: "",
}
});

export default definePlugin({
name: "HideMedia",
description: "Hide attachments and embeds for individual messages via hover button",
authors: [Devs.Ven],
authors: [Devs.Ven, Devs.aiko],
dependencies: ["MessageUpdaterAPI"],

patches: [{
Expand All @@ -55,13 +208,15 @@ export default definePlugin({
}
}],

renderMessagePopoverButton(msg) {
settings,

renderMessagePopoverButton(msg: IMessage) {
// @ts-ignore - discord-types lags behind discord.
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
const hasAttachmentsInSnapshots = msg.messageSnapshots.some(
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length || snapshot?.message.embeds.length
);

if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInSnapshots) return null;

const isHidden = hiddenMessages.has(msg.id);

Expand All @@ -70,7 +225,7 @@ export default definePlugin({
icon: isHidden ? ImageVisible : ImageInvisible,
message: msg,
channel: ChannelStore.getChannel(msg.channel_id),
onClick: () => this.toggleHide(msg.channel_id, msg.id)
onClick: () => toggleHide(msg.channel_id, msg.id)
};
},

Expand All @@ -84,6 +239,22 @@ export default definePlugin({
);
},

flux: {
async LOAD_MESSAGES_SUCCESS(payload: ILoadMessagesSuccessPayload) {
for (const message of payload.messages) {
await checkAndHide(message, settings.store);
}
},

async MESSAGE_CREATE({ message }: IMessageCreatePayload) {
await checkAndHide(message, settings.store);
},

async MESSAGE_UPDATE({ message }: IMessageUpdatePayload) {
await checkAndHide(message, settings.store);
}
},

async start() {
await getHiddenMessages();
},
Expand All @@ -94,14 +265,5 @@ export default definePlugin({

shouldHide(messageId: string) {
return hiddenMessages.has(messageId);
},

async toggleHide(channelId: string, messageId: string) {
const ids = await getHiddenMessages();
if (!ids.delete(messageId))
ids.add(messageId);

await saveHiddenMessages(ids);
updateMessage(channelId, messageId);
}
});
44 changes: 44 additions & 0 deletions src/plugins/hideAttachments/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { Message } from "discord-types/general";

export interface ILoadMessagesSuccessPayload {
channelId: string;
messages: Array<Message>;
}

export interface IMessage extends Message {
message_reference?: {
type: number;
channel_id: string;
message_id: string;
guild_id: string;
},
message_snapshots?: {
message: Message;
}[]
}

export interface IMessageCreatePayload {
message: IMessage;
}

export interface IMessageUpdatePayload {
message: IMessage;
}
22 changes: 22 additions & 0 deletions src/plugins/hideAttachments/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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/>.
*/

export function isStringEmpty (str: string) {
if (!str) return false;
return str.trim().length === 0;
}
4 changes: 4 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "jamesbt365",
id: 158567567487795200n,
},
aiko: {
name: "kima_riiiiiii",
id: 366434327761911808n
},
samsam: {
name: "samsam",
id: 836452332387565589n,
Expand Down