feat(plugin): RelationshipNotifier (#450)
Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
parent
dae7cb67ef
commit
2c8ebdce7d
7 changed files with 465 additions and 0 deletions
40
src/plugins/relationshipNotifier/events.ts
Normal file
40
src/plugins/relationshipNotifier/events.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { FluxEvents } from "@webpack/types";
|
||||||
|
|
||||||
|
import { onChannelDelete, onGuildDelete, onRelationshipRemove } from "./functions";
|
||||||
|
import { syncFriends, syncGroups, syncGuilds } from "./utils";
|
||||||
|
|
||||||
|
export const FluxHandlers: Partial<Record<FluxEvents, Array<(data: any) => void>>> = {
|
||||||
|
GUILD_CREATE: [syncGuilds],
|
||||||
|
GUILD_DELETE: [onGuildDelete],
|
||||||
|
CHANNEL_CREATE: [syncGroups],
|
||||||
|
CHANNEL_DELETE: [onChannelDelete],
|
||||||
|
RELATIONSHIP_ADD: [syncFriends],
|
||||||
|
RELATIONSHIP_UPDATE: [syncFriends],
|
||||||
|
RELATIONSHIP_REMOVE: [syncFriends, onRelationshipRemove]
|
||||||
|
};
|
||||||
|
|
||||||
|
export function forEachEvent(fn: (event: FluxEvents, handler: (data: any) => void) => void) {
|
||||||
|
for (const event in FluxHandlers) {
|
||||||
|
for (const cb of FluxHandlers[event]) {
|
||||||
|
fn(event as FluxEvents, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
src/plugins/relationshipNotifier/functions.ts
Normal file
87
src/plugins/relationshipNotifier/functions.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { UserUtils } from "@webpack/common";
|
||||||
|
|
||||||
|
import settings from "./settings";
|
||||||
|
import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
|
||||||
|
import { deleteGroup, deleteGuild, getGroup, getGuild, notify } from "./utils";
|
||||||
|
|
||||||
|
let manuallyRemovedFriend: string | undefined;
|
||||||
|
let manuallyRemovedGuild: string | undefined;
|
||||||
|
let manuallyRemovedGroup: string | undefined;
|
||||||
|
|
||||||
|
export const removeFriend = (id: string) => manuallyRemovedFriend = id;
|
||||||
|
export const removeGuild = (id: string) => manuallyRemovedGuild = id;
|
||||||
|
export const removeGroup = (id: string) => manuallyRemovedGroup = id;
|
||||||
|
|
||||||
|
export async function onRelationshipRemove({ relationship: { type, id } }: RelationshipRemove) {
|
||||||
|
if (manuallyRemovedFriend === id) {
|
||||||
|
manuallyRemovedFriend = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await UserUtils.fetchUser(id)
|
||||||
|
.catch(() => null);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case RelationshipType.FRIEND:
|
||||||
|
if (settings.store.friends)
|
||||||
|
notify(`${user.tag} removed you as a friend.`, user.getAvatarURL(undefined, undefined, false));
|
||||||
|
break;
|
||||||
|
case RelationshipType.FRIEND_REQUEST:
|
||||||
|
if (settings.store.friendRequestCancels)
|
||||||
|
notify(`A friend request from ${user.tag} has been removed.`, user.getAvatarURL(undefined, undefined, false));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onGuildDelete({ guild: { id, unavailable } }: GuildDelete) {
|
||||||
|
if (!settings.store.servers) return;
|
||||||
|
if (unavailable) return;
|
||||||
|
|
||||||
|
if (manuallyRemovedGuild === id) {
|
||||||
|
deleteGuild(id);
|
||||||
|
manuallyRemovedGuild = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const guild = getGuild(id);
|
||||||
|
if (guild) {
|
||||||
|
deleteGuild(id);
|
||||||
|
notify(`You were removed from the server ${guild.name}.`, guild.iconURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onChannelDelete({ channel: { id, type } }: ChannelDelete) {
|
||||||
|
if (!settings.store.groups) return;
|
||||||
|
if (type !== ChannelType.GROUP_DM) return;
|
||||||
|
|
||||||
|
if (manuallyRemovedGroup === id) {
|
||||||
|
deleteGroup(id);
|
||||||
|
manuallyRemovedGroup = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = getGroup(id);
|
||||||
|
if (group) {
|
||||||
|
deleteGroup(id);
|
||||||
|
notify(`You were removed from the group ${group.name}.`, group.iconURL);
|
||||||
|
}
|
||||||
|
}
|
70
src/plugins/relationshipNotifier/index.ts
Normal file
70
src/plugins/relationshipNotifier/index.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
|
||||||
|
import { forEachEvent } from "./events";
|
||||||
|
import { removeFriend, removeGroup, removeGuild } from "./functions";
|
||||||
|
import settings from "./settings";
|
||||||
|
import { syncAndRunChecks } from "./utils";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "RelationshipNotifier",
|
||||||
|
description: "Notifies you when a friend, group chat, or server removes you.",
|
||||||
|
authors: [Devs.nick],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "removeRelationship:function(",
|
||||||
|
replacement: {
|
||||||
|
match: /(removeRelationship:function\((\i),\i,\i\){)/,
|
||||||
|
replace: "$1$self.removeFriend($2);"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "leaveGuild:function(",
|
||||||
|
replacement: {
|
||||||
|
match: /(leaveGuild:function\((\i)\){)/,
|
||||||
|
replace: "$1$self.removeGuild($2);"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "closePrivateChannel:function(",
|
||||||
|
replacement: {
|
||||||
|
match: /(closePrivateChannel:function\((\i)\){)/,
|
||||||
|
replace: "$1$self.removeGroup($2);"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await syncAndRunChecks();
|
||||||
|
forEachEvent((ev, cb) => FluxDispatcher.subscribe(ev, cb));
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
forEachEvent((ev, cb) => FluxDispatcher.unsubscribe(ev, cb));
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFriend,
|
||||||
|
removeGroup,
|
||||||
|
removeGuild
|
||||||
|
});
|
53
src/plugins/relationshipNotifier/settings.ts
Normal file
53
src/plugins/relationshipNotifier/settings.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { definePluginSettings } from "@api/settings";
|
||||||
|
import { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
export default definePluginSettings({
|
||||||
|
notices: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Also show a notice at the top of your screen when removed (use this if you don't want to miss any notifications).",
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
offlineRemovals: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Notify you when starting discord if you were removed while offline.",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
friends: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Notify when a friend removes you",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
friendRequestCancels: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Notify when a friend request is cancelled",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
servers: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Notify when removed from a server",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Notify when removed from a group chat",
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
});
|
62
src/plugins/relationshipNotifier/types.ts
Normal file
62
src/plugins/relationshipNotifier/types.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { Channel } from "discord-types/general";
|
||||||
|
|
||||||
|
export interface ChannelDelete {
|
||||||
|
type: "CHANNEL_DELETE";
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuildDelete {
|
||||||
|
type: "GUILD_DELETE";
|
||||||
|
guild: {
|
||||||
|
id: string;
|
||||||
|
unavailable?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelationshipRemove {
|
||||||
|
type: "RELATIONSHIP_REMOVE";
|
||||||
|
relationship: {
|
||||||
|
id: string;
|
||||||
|
nickname: string;
|
||||||
|
type: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleGroupChannel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
iconURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleGuild {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
iconURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum ChannelType {
|
||||||
|
GROUP_DM = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum RelationshipType {
|
||||||
|
FRIEND = 1,
|
||||||
|
FRIEND_REQUEST = 3,
|
||||||
|
}
|
149
src/plugins/relationshipNotifier/utils.ts
Normal file
149
src/plugins/relationshipNotifier/utils.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 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 { DataStore, Notices } from "@api/index";
|
||||||
|
import { showNotification } from "@api/Notifications";
|
||||||
|
import { ChannelStore, GuildStore, RelationshipStore, UserUtils } from "@webpack/common";
|
||||||
|
|
||||||
|
import settings from "./settings";
|
||||||
|
import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
|
||||||
|
|
||||||
|
const guilds = new Map<string, SimpleGuild>();
|
||||||
|
const groups = new Map<string, SimpleGroupChannel>();
|
||||||
|
const friends = {
|
||||||
|
friends: [] as string[],
|
||||||
|
requests: [] as string[]
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function syncAndRunChecks() {
|
||||||
|
const [oldGuilds, oldGroups, oldFriends] = await DataStore.getMany([
|
||||||
|
"relationship-notifier-guilds",
|
||||||
|
"relationship-notifier-groups",
|
||||||
|
"relationship-notifier-friends"
|
||||||
|
]) as [Map<string, SimpleGuild> | undefined, Map<string, SimpleGroupChannel> | undefined, Record<"friends" | "requests", string[]> | undefined];
|
||||||
|
|
||||||
|
await Promise.all([syncGuilds(), syncGroups(), syncFriends()]);
|
||||||
|
|
||||||
|
if (settings.store.offlineRemovals) {
|
||||||
|
if (settings.store.groups && oldGroups?.size) {
|
||||||
|
for (const [id, group] of oldGroups) {
|
||||||
|
if (!groups.has(id))
|
||||||
|
notify(`You are no longer in the group ${group.name}.`, group.iconURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.servers && oldGuilds?.size) {
|
||||||
|
for (const [id, guild] of oldGuilds) {
|
||||||
|
if (!guilds.has(id))
|
||||||
|
notify(`You are no longer in the server ${guild.name}.`, guild.iconURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.friends && oldFriends?.friends.length) {
|
||||||
|
for (const id of oldFriends.friends) {
|
||||||
|
if (friends.friends.includes(id)) continue;
|
||||||
|
|
||||||
|
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
||||||
|
if (user)
|
||||||
|
notify(`You are no longer friends with ${user.tag}.`, user.getAvatarURL(undefined, undefined, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.store.friendRequestCancels && oldFriends?.requests?.length) {
|
||||||
|
for (const id of oldFriends.requests) {
|
||||||
|
if (friends.requests.includes(id)) continue;
|
||||||
|
|
||||||
|
const user = await UserUtils.fetchUser(id).catch(() => void 0);
|
||||||
|
if (user)
|
||||||
|
notify(`Friend request from ${user.tag} has been revoked.`, user.getAvatarURL(undefined, undefined, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notify(text: string, icon?: string) {
|
||||||
|
if (settings.store.notices)
|
||||||
|
Notices.showNotice(text, "OK", () => Notices.popNotice());
|
||||||
|
|
||||||
|
showNotification({
|
||||||
|
title: "Relationship Notifier",
|
||||||
|
body: text,
|
||||||
|
icon
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGuild(id: string) {
|
||||||
|
return guilds.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteGuild(id: string) {
|
||||||
|
guilds.delete(id);
|
||||||
|
syncGuilds();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncGuilds() {
|
||||||
|
for (const [id, { name, icon }] of Object.entries(GuildStore.getGuilds())) {
|
||||||
|
guilds.set(id, {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
iconURL: icon && `https://cdn.discordapp.com/icons/${id}/${icon}.png`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await DataStore.set("relationship-notifier-guilds", guilds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGroup(id: string) {
|
||||||
|
return groups.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteGroup(id: string) {
|
||||||
|
groups.delete(id);
|
||||||
|
syncGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncGroups() {
|
||||||
|
for (const { type, id, name, rawRecipients, icon } of ChannelStore.getSortedPrivateChannels()) {
|
||||||
|
if (type === ChannelType.GROUP_DM)
|
||||||
|
groups.set(id, {
|
||||||
|
id,
|
||||||
|
name: name || rawRecipients.map(r => r.username).join(", "),
|
||||||
|
iconURL: icon && `https://cdn.discordapp.com/channel-icons/${id}/${icon}.png`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await DataStore.set("relationship-notifier-groups", groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncFriends() {
|
||||||
|
friends.friends = [];
|
||||||
|
friends.requests = [];
|
||||||
|
|
||||||
|
const relationShips = RelationshipStore.getRelationships();
|
||||||
|
for (const id in relationShips) {
|
||||||
|
switch (relationShips[id]) {
|
||||||
|
case RelationshipType.FRIEND:
|
||||||
|
friends.friends.push(id);
|
||||||
|
break;
|
||||||
|
case RelationshipType.FRIEND_REQUEST:
|
||||||
|
friends.requests.push(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await DataStore.set("relationship-notifier-friends", friends);
|
||||||
|
}
|
|
@ -194,6 +194,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "Captain",
|
name: "Captain",
|
||||||
id: 347366054806159360n
|
id: 347366054806159360n
|
||||||
},
|
},
|
||||||
|
nick: {
|
||||||
|
name: "nick",
|
||||||
|
id: 347884694408265729n
|
||||||
|
},
|
||||||
whqwert: {
|
whqwert: {
|
||||||
name: "whqwert",
|
name: "whqwert",
|
||||||
id: 586239091520176128n
|
id: 586239091520176128n
|
||||||
|
|
Loading…
Reference in a new issue