VencordRPC: 👋
This commit is contained in:
parent
eb64db046c
commit
628554a920
1 changed files with 0 additions and 421 deletions
|
|
@ -1,421 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Activity, Channel, FluxStore } from "@vencord/discord-types";
|
||||
import { ActivityType } from "@vencord/discord-types/enums";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, ChannelStore, FluxDispatcher, GuildStore, IconUtils, PresenceStore, RelationshipStore, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||
|
||||
const presenceStore = findByPropsLazy("getLocalPresence");
|
||||
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
|
||||
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
|
||||
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
|
||||
};
|
||||
const VoiceStates = findByPropsLazy("getVoiceStatesForChannel");
|
||||
const chino = "https://i.imgur.com/Dsa2rQy.png";
|
||||
const wysi = "https://i.imgur.com/uKtXde9.gif";
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||
}
|
||||
|
||||
const enum TimestampMode {
|
||||
NONE,
|
||||
NOW,
|
||||
TIME,
|
||||
CUSTOM,
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
appID: {
|
||||
type: OptionType.STRING,
|
||||
description: "Application ID (required)",
|
||||
onChange: onChange,
|
||||
isValid: (value: string) => {
|
||||
if (!value) return "Application ID is required.";
|
||||
if (value && !/^\d+$/.test(value)) return "Application ID must be a number.";
|
||||
return true;
|
||||
}
|
||||
},
|
||||
userAvatarAsSmallImage: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Use your avatar as small image",
|
||||
onChange: onChange,
|
||||
default: false
|
||||
},
|
||||
exposeDmsUsername: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Expose current DMs username",
|
||||
onChange: onChange,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Activity type",
|
||||
onChange: onChange,
|
||||
options: [
|
||||
{
|
||||
label: "Playing",
|
||||
value: ActivityType.PLAYING,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "Streaming",
|
||||
value: ActivityType.STREAMING
|
||||
},
|
||||
{
|
||||
label: "Listening",
|
||||
value: ActivityType.LISTENING
|
||||
},
|
||||
{
|
||||
label: "Watching",
|
||||
value: ActivityType.WATCHING
|
||||
},
|
||||
{
|
||||
label: "Competing",
|
||||
value: ActivityType.COMPETING
|
||||
}
|
||||
]
|
||||
},
|
||||
streamLink: {
|
||||
type: OptionType.STRING,
|
||||
description: "Twitch.tv or Youtube.com link (only for Streaming activity type)",
|
||||
onChange: onChange,
|
||||
disabled: isStreamLinkDisabled,
|
||||
isValid: isStreamLinkValid
|
||||
},
|
||||
timestampMode: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Timestamp mode",
|
||||
onChange: onChange,
|
||||
options: [
|
||||
{
|
||||
label: "None",
|
||||
value: TimestampMode.NONE,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "Since discord open",
|
||||
value: TimestampMode.NOW
|
||||
},
|
||||
{
|
||||
label: "Same as your current time",
|
||||
value: TimestampMode.TIME
|
||||
},
|
||||
{
|
||||
label: "Custom",
|
||||
value: TimestampMode.CUSTOM
|
||||
}
|
||||
]
|
||||
},
|
||||
startTime: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Start timestamp (only for custom timestamp mode)",
|
||||
onChange: onChange,
|
||||
disabled: isTimestampDisabled,
|
||||
isValid: (value: number) => {
|
||||
if (value && value < 0) return "Start timestamp must be greater than 0.";
|
||||
return true;
|
||||
}
|
||||
},
|
||||
endTime: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "End timestamp (only for custom timestamp mode)",
|
||||
onChange: onChange,
|
||||
disabled: isTimestampDisabled,
|
||||
isValid: (value: number) => {
|
||||
if (value && value < 0) return "End timestamp must be greater than 0.";
|
||||
return true;
|
||||
}
|
||||
},
|
||||
secretStuff: {
|
||||
type: OptionType.STRING,
|
||||
description: "A list of server/channel ids separated by commas, which are hidden from the RPC.",
|
||||
onChange: onChange,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
function onChange() {
|
||||
setRpc(true);
|
||||
setRpc();
|
||||
}
|
||||
|
||||
function isStreamLinkDisabled() {
|
||||
return settings.store.type !== ActivityType.STREAMING;
|
||||
}
|
||||
|
||||
function isStreamLinkValid(value: string) {
|
||||
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTimestampDisabled() {
|
||||
return settings.store.timestampMode !== TimestampMode.CUSTOM;
|
||||
}
|
||||
|
||||
function onlineFriendCount(): number {
|
||||
let onlineFriends = 0;
|
||||
const relationships = RelationshipStore.getFriendIDs();
|
||||
for (const id of relationships) {
|
||||
if (PresenceStore.getStatus(id) !== "offline") {
|
||||
onlineFriends++;
|
||||
}
|
||||
}
|
||||
return onlineFriends;
|
||||
}
|
||||
|
||||
function totalFriendCount(): number {
|
||||
return RelationshipStore.getFriendCount();
|
||||
}
|
||||
|
||||
function memberCount(): string {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const guildId = SelectedGuildStore.getGuildId()!;
|
||||
const { groups } = ChannelMemberStore.getProps(guildId, channelId);
|
||||
const total = GuildMemberCountStore.getMemberCount(guildId);
|
||||
|
||||
if (total == null)
|
||||
return "";
|
||||
|
||||
const online =
|
||||
(groups.length === 1 && groups[0].id === "unknown")
|
||||
? 0
|
||||
: groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
|
||||
|
||||
return online === 0 ? `${total} members` : `${online} online / ${total} total`;
|
||||
}
|
||||
|
||||
function getChannelIconURL(channel: Channel): string {
|
||||
if (channel.icon) return `https://cdn.discordapp.com/channel-icons/${channel.id}/${channel.icon}.webp?size=128`;
|
||||
return chino;
|
||||
}
|
||||
|
||||
function getSecretStuff(): string[] {
|
||||
return settings.store.secretStuff.split(",").map(id => id.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
async function createActivity(): Promise<Activity | undefined> {
|
||||
|
||||
const {
|
||||
appID,
|
||||
userAvatarAsSmallImage,
|
||||
exposeDmsUsername,
|
||||
streamLink,
|
||||
timestampMode,
|
||||
startTime,
|
||||
endTime,
|
||||
} = settings.store;
|
||||
|
||||
let { type } = settings.store;
|
||||
|
||||
let appName = "Equicord";
|
||||
let details = "";
|
||||
let state = "";
|
||||
let imageBig = "";
|
||||
const imageBigTooltip = undefined;
|
||||
let imageSmall = "";
|
||||
const imageSmallTooltip = undefined;
|
||||
let buttonOneText: string | undefined;
|
||||
let buttonOneURL: string | undefined;
|
||||
let buttonTwoText: string | undefined;
|
||||
let buttonTwoURL: string | undefined;
|
||||
|
||||
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const guildId = SelectedGuildStore.getGuildId()!;
|
||||
const voiceId = SelectedChannelStore.getVoiceChannelId();
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
if (userAvatarAsSmallImage) imageSmall = currentUser.getAvatarURL(undefined, undefined, true) || chino;
|
||||
|
||||
const secretStuff = getSecretStuff();
|
||||
const isSecret = secretStuff.includes(channelId) || secretStuff.includes(guildId);
|
||||
|
||||
if (isSecret) {
|
||||
appName = "Hidden Channel/Guild";
|
||||
details = "#secret";
|
||||
state = "hehe";
|
||||
imageBig = chino;
|
||||
} else if (!channelId) {
|
||||
appName = "Friends List";
|
||||
details = `${onlineFriendCount()} online / ${totalFriendCount()} total`;
|
||||
state = `${GuildStore.getGuildCount()} servers`;
|
||||
imageBig = chino;
|
||||
} else {
|
||||
if (channelId === "@home" || channelId === "customize-community" || channelId === "channel-browser" || channelId === "onboarding") {
|
||||
appName = channelId === "@home" ? "Server Guide" : channelId === "customize-community" ? "Channels & Roles" : channelId === "channel-browser" ? "Browse Channels" : "Onboarding";
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
if (guild) {
|
||||
details = guild.name;
|
||||
state = memberCount();
|
||||
imageBig = IconUtils.getGuildIconURL({ id: guild.id, icon: guild.icon, canAnimate: true, size: 512 }) || chino;
|
||||
if (guild.vanityURLCode) {
|
||||
buttonOneText = `Join ${guild.name.slice(0, 26)}`;
|
||||
buttonOneURL = `https://discord.gg/${guild.vanityURLCode}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
|
||||
if (channel.isDM()) {
|
||||
const recipient = UserStore.getUser(channel.recipients[0]);
|
||||
appName = exposeDmsUsername ? `${recipient.username}'s DM` : "Direct Messages";
|
||||
details = `${onlineFriendCount()} online / ${totalFriendCount()} total`;
|
||||
state = `${GuildStore.getGuildCount()} servers`;
|
||||
imageBig = recipient.getAvatarURL(undefined, undefined, true) || chino;
|
||||
}
|
||||
|
||||
if (channel.isGroupDM()) {
|
||||
appName = channel.name || "Group DM";
|
||||
details = `${channel.recipients.length + 1} members`;
|
||||
imageBig = getChannelIconURL(channel);
|
||||
}
|
||||
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
if (guild) {
|
||||
appName = `#${channel.name}`;
|
||||
details = guild.name;
|
||||
state = memberCount();
|
||||
imageBig = IconUtils.getGuildIconURL({ id: guild.id, icon: guild.icon, canAnimate: true, size: 512 }) || chino;
|
||||
if (guild.vanityURLCode) {
|
||||
buttonOneText = `Join ${guild.name.slice(0, 31 - 5)}`;
|
||||
buttonOneURL = `https://discord.gg/${guild.vanityURLCode}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (voiceId) {
|
||||
const voiceChannel = ChannelStore.getChannel(voiceId);
|
||||
const voiceGuild = GuildStore.getGuild(voiceChannel.guild_id);
|
||||
const voiceMemberCount = Object.keys(VoiceStates.getVoiceStatesForChannel(voiceChannel.id)).length;
|
||||
buttonTwoText = `🔊 ${voiceGuild.name.slice(0, 31 - 6 - voiceMemberCount.toString().length)} [${voiceMemberCount}]`;
|
||||
buttonTwoURL = `https://discordapp.com/channels/${voiceGuild.id}/${voiceChannel.id}`;
|
||||
|
||||
if (!buttonOneText) {
|
||||
buttonOneText = buttonTwoText;
|
||||
buttonOneURL = buttonTwoURL;
|
||||
buttonTwoText = undefined;
|
||||
buttonTwoURL = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if ((new Date().getHours() === 7 || new Date().getHours() === 19) && new Date().getMinutes() === 27) {
|
||||
type = ActivityType.PLAYING;
|
||||
appName = "WHEN YOU SEE IT";
|
||||
imageBig = wysi;
|
||||
details = "7:27 👈";
|
||||
state = "";
|
||||
}
|
||||
|
||||
const activity: Activity = {
|
||||
application_id: appID || "0",
|
||||
name: appName,
|
||||
state,
|
||||
details,
|
||||
type,
|
||||
flags: 1 << 0,
|
||||
};
|
||||
|
||||
if (type === ActivityType.STREAMING) activity.url = streamLink;
|
||||
|
||||
switch (timestampMode) {
|
||||
case TimestampMode.NOW:
|
||||
activity.timestamps = {
|
||||
start: Date.now()
|
||||
};
|
||||
break;
|
||||
case TimestampMode.TIME:
|
||||
activity.timestamps = {
|
||||
start: Date.now() - (new Date().getHours() * 3600 + new Date().getMinutes() * 60 + new Date().getSeconds()) * 1000
|
||||
};
|
||||
break;
|
||||
case TimestampMode.CUSTOM:
|
||||
if (startTime || endTime) {
|
||||
activity.timestamps = {};
|
||||
if (startTime) activity.timestamps.start = startTime;
|
||||
if (endTime) activity.timestamps.end = endTime;
|
||||
}
|
||||
break;
|
||||
case TimestampMode.NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (buttonOneText) {
|
||||
activity.buttons = [
|
||||
buttonOneText,
|
||||
buttonTwoText
|
||||
].filter(isTruthy);
|
||||
|
||||
activity.metadata = {
|
||||
button_urls: [
|
||||
buttonOneURL,
|
||||
buttonTwoURL
|
||||
].filter(isTruthy)
|
||||
};
|
||||
}
|
||||
|
||||
if (imageBig) {
|
||||
activity.assets = {
|
||||
large_image: await getApplicationAsset(imageBig),
|
||||
large_text: imageBigTooltip || undefined
|
||||
};
|
||||
}
|
||||
|
||||
if (imageSmall) {
|
||||
activity.assets = {
|
||||
...activity.assets,
|
||||
small_image: await getApplicationAsset(imageSmall),
|
||||
small_text: imageSmallTooltip || undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
for (const k in activity) {
|
||||
if (k === "type") continue;
|
||||
const v = activity[k];
|
||||
if (!v || v.length === 0)
|
||||
delete activity[k];
|
||||
}
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
async function setRpc(disable?: boolean) {
|
||||
const activities: any = presenceStore.getActivities();
|
||||
const activity: Activity | undefined = !activities.length || (activities.length === 1 && activities[0].application_id === settings.store.appID) ? await createActivity() : undefined;
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "LOCAL_ACTIVITY_UPDATE",
|
||||
activity: !disable ? activity : null,
|
||||
socketId: "CustomRPC",
|
||||
});
|
||||
|
||||
if (!disable) {
|
||||
timeout = setTimeout(() => setRpc(), 4000);
|
||||
} else if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "VencordRPC",
|
||||
description: "how to expose yourself",
|
||||
authors: [Devs.AutumnVN],
|
||||
start: setRpc,
|
||||
stop: () => setRpc(true),
|
||||
settings
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue