Merge AllCallTimers into CallTimer (#612)
This commit is contained in:
parent
83e528cbf5
commit
1a507b0868
6 changed files with 236 additions and 277 deletions
|
|
@ -1,242 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { PassiveUpdateState, VoiceState } from "@vencord/discord-types";
|
||||
import { FluxDispatcher, GuildStore, UserStore } from "@webpack/common";
|
||||
|
||||
import { Timer } from "./Timer";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
showWithoutHover: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Always show the timer without needing to hover",
|
||||
restartNeeded: true,
|
||||
default: true
|
||||
},
|
||||
showRoleColor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show the user's role color (if this plugin in enabled)",
|
||||
restartNeeded: false,
|
||||
default: true
|
||||
},
|
||||
trackSelf: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Also track yourself",
|
||||
restartNeeded: false,
|
||||
default: true
|
||||
},
|
||||
showSeconds: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show seconds in the timer",
|
||||
restartNeeded: false,
|
||||
default: true
|
||||
},
|
||||
format: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Compact or human readable format:",
|
||||
options: [
|
||||
{
|
||||
label: "30:23:00:42",
|
||||
value: "stopwatch",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "30d 23h 00m 42s",
|
||||
value: "human"
|
||||
}
|
||||
]
|
||||
},
|
||||
watchLargeGuilds: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Track users in large guilds. This may cause lag if you're in a lot of large guilds with active voice users. Tested with up to 2000 active voice users with no issues.",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
// Save the join time of all users in a Map
|
||||
type userJoinData = { channelId: string, time: number; guildId: string; };
|
||||
const userJoinTimes = new Map<string, userJoinData>();
|
||||
|
||||
/**
|
||||
* The function `addUserJoinTime` stores the join time of a user in a specific channel within a guild.
|
||||
* @param {string} userId - The `userId` parameter is a string that represents the unique identifier of
|
||||
* the user who is joining a channel in a guild.
|
||||
* @param {string} channelId - The `channelId` parameter represents the unique identifier of the
|
||||
* channel where the user joined.
|
||||
* @param {string} guildId - The `guildId` parameter in the `addUserJoinTime` function represents the
|
||||
* unique identifier of the guild (server) to which the user belongs. It is used to associate the
|
||||
* user's join time with a specific guild within the application or platform.
|
||||
*/
|
||||
function addUserJoinTime(userId: string, channelId: string, guildId: string) {
|
||||
// create a random number
|
||||
userJoinTimes.set(userId, { channelId, time: Date.now(), guildId });
|
||||
}
|
||||
|
||||
/**
|
||||
* The function `removeUserJoinTime` removes the join time of a user identified by their user ID.
|
||||
* @param {string} userId - The `userId` parameter is a string that represents the unique identifier of
|
||||
* a user whose join time needs to be removed.
|
||||
*/
|
||||
function removeUserJoinTime(userId: string) {
|
||||
userJoinTimes.delete(userId);
|
||||
}
|
||||
|
||||
// For every user, channelId and oldChannelId will differ when moving channel.
|
||||
// Only for the local user, channelId and oldChannelId will be the same when moving channel,
|
||||
// for some ungodly reason
|
||||
let myLastChannelId: string | undefined;
|
||||
|
||||
// Allow user updates on discord first load
|
||||
let runOneTime = true;
|
||||
|
||||
export default definePlugin({
|
||||
name: "AllCallTimers",
|
||||
description: "Add call timer to all users in a server voice channel.",
|
||||
authors: [EquicordDevs.MaxHerbold, Devs.D3SOX],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: ".usernameSpeaking]",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=user:(\i).*?)iconGroup,.{0,200}children:\[/,
|
||||
replace: "$&$self.renderTimer($1.id),",
|
||||
predicate: () => !settings.store.showWithoutHover,
|
||||
},
|
||||
{
|
||||
match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/,
|
||||
replace: "$&,$self.renderTimer($1.id),",
|
||||
predicate: () => settings.store.showWithoutHover,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
for (const state of voiceStates) {
|
||||
const { userId, channelId, guildId } = state;
|
||||
const isMe = userId === myId;
|
||||
|
||||
if (!guildId) {
|
||||
// guildId is never undefined here
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the state does not actually has a `oldChannelId` property
|
||||
if (!("oldChannelId" in state) && !runOneTime && !settings.store.watchLargeGuilds) {
|
||||
// batch update triggered. This is ignored because it
|
||||
// is caused by opening a previously unopened guild
|
||||
continue;
|
||||
}
|
||||
|
||||
let { oldChannelId } = state;
|
||||
if (isMe && channelId !== myLastChannelId) {
|
||||
oldChannelId = myLastChannelId;
|
||||
myLastChannelId = channelId ?? undefined;
|
||||
}
|
||||
|
||||
if (channelId !== oldChannelId) {
|
||||
if (channelId) {
|
||||
// move or join
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
} else if (oldChannelId) {
|
||||
// leave
|
||||
removeUserJoinTime(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
runOneTime = false;
|
||||
},
|
||||
PASSIVE_UPDATE_V1(passiveUpdate: PassiveUpdateState) {
|
||||
if (settings.store.watchLargeGuilds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { voiceStates } = passiveUpdate;
|
||||
if (!voiceStates) {
|
||||
// if there are no users in a voice call
|
||||
return;
|
||||
}
|
||||
|
||||
// find all users that have the same guildId and if that user is not in the voiceStates, remove them from the map
|
||||
const { guildId } = passiveUpdate;
|
||||
|
||||
// check the guildId in the userJoinTimes map
|
||||
for (const [userId, data] of userJoinTimes) {
|
||||
if (data.guildId === guildId) {
|
||||
// check if the user is in the voiceStates
|
||||
const userInVoiceStates = voiceStates.find(state => state.userId === userId);
|
||||
if (!userInVoiceStates) {
|
||||
// remove the user from the map
|
||||
removeUserJoinTime(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since we were gifted this data let's use it to update our join times
|
||||
for (const state of voiceStates) {
|
||||
const { userId, channelId } = state;
|
||||
|
||||
if (!channelId) {
|
||||
// channelId is never undefined here
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the user is in the map
|
||||
if (userJoinTimes.has(userId)) {
|
||||
// check if the user is in a channel
|
||||
if (channelId !== userJoinTimes.get(userId)?.channelId) {
|
||||
// update the user's join time
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
}
|
||||
} else {
|
||||
// user wasn't previously tracked, add the user to the map
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
subscribeToAllGuilds() {
|
||||
// we need to subscribe to all guilds' events because otherwise we would miss updates on large guilds
|
||||
const guilds = Object.values(GuildStore.getGuilds()).map(guild => guild.id);
|
||||
const subscriptions = guilds.reduce((acc, id) => ({ ...acc, [id]: { typing: true } }), {});
|
||||
FluxDispatcher.dispatch({ type: "GUILD_SUBSCRIPTIONS_FLUSH", subscriptions });
|
||||
},
|
||||
|
||||
start() {
|
||||
if (settings.store.watchLargeGuilds) {
|
||||
this.subscribeToAllGuilds();
|
||||
}
|
||||
},
|
||||
|
||||
renderTimer(userId: string) {
|
||||
// get the user join time from the users object
|
||||
const joinTime = userJoinTimes.get(userId);
|
||||
if (!joinTime?.time) {
|
||||
// join time is unknown
|
||||
return;
|
||||
}
|
||||
if (userId === UserStore.getCurrentUser().id && !settings.store.trackSelf) {
|
||||
// don't show for self
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Timer time={joinTime.time} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
[class*="panels"] [class*="inner"],
|
||||
[class*="rtcConnectionStatus"] {
|
||||
height: fit-content !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,28 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { definePluginSettings, migratePluginToSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { useTimer } from "@utils/react";
|
||||
import { formatDurationMs } from "@utils/text";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
import { PassiveUpdateState, VoiceState } from "@vencord/discord-types";
|
||||
import { FluxDispatcher, GuildStore, React, UserStore } from "@webpack/common";
|
||||
|
||||
import alignedChatInputFix from "./alignedChatInputFix.css?managed";
|
||||
import { Timer } from "./Timer";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
export const settings = definePluginSettings({
|
||||
format: {
|
||||
type: OptionType.SELECT,
|
||||
description: "The timer format. This can be any valid moment.js format",
|
||||
description: "Compact or human readable format:",
|
||||
options: [
|
||||
{
|
||||
label: "30d 23:00:42",
|
||||
label: "30:23:00:42",
|
||||
value: "stopwatch",
|
||||
default: true
|
||||
},
|
||||
|
|
@ -42,34 +32,245 @@ const settings = definePluginSettings({
|
|||
}
|
||||
]
|
||||
},
|
||||
allCallTimers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Add call timer to all users in a server voice channel",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
showWithoutHover: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Always show the timer without needing to hover",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
showRoleColor: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show the user's role color (if this plugin in enabled)",
|
||||
restartNeeded: false,
|
||||
default: false
|
||||
},
|
||||
trackSelf: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Also track yourself",
|
||||
restartNeeded: false,
|
||||
default: false
|
||||
},
|
||||
showSeconds: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show seconds in the timer",
|
||||
restartNeeded: false,
|
||||
default: false
|
||||
},
|
||||
watchLargeGuilds: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Track users in large guilds. This may cause lag if you're in a lot of large guilds with active voice users. Tested with up to 2000 active voice users with no issues.",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
// Save the join time of all users in a Map
|
||||
type userJoinData = { channelId: string, time: number; guildId: string; };
|
||||
const userJoinTimes = new Map<string, userJoinData>();
|
||||
|
||||
/**
|
||||
* The function `addUserJoinTime` stores the join time of a user in a specific channel within a guild.
|
||||
* @param {string} userId - The `userId` parameter is a string that represents the unique identifier of
|
||||
* the user who is joining a channel in a guild.
|
||||
* @param {string} channelId - The `channelId` parameter represents the unique identifier of the
|
||||
* channel where the user joined.
|
||||
* @param {string} guildId - The `guildId` parameter in the `addUserJoinTime` function represents the
|
||||
* unique identifier of the guild (server) to which the user belongs. It is used to associate the
|
||||
* user's join time with a specific guild within the application or platform.
|
||||
*/
|
||||
function addUserJoinTime(userId: string, channelId: string, guildId: string) {
|
||||
// create a random number
|
||||
userJoinTimes.set(userId, { channelId, time: Date.now(), guildId });
|
||||
}
|
||||
|
||||
/**
|
||||
* The function `removeUserJoinTime` removes the join time of a user identified by their user ID.
|
||||
* @param {string} userId - The `userId` parameter is a string that represents the unique identifier of
|
||||
* a user whose join time needs to be removed.
|
||||
*/
|
||||
function removeUserJoinTime(userId: string) {
|
||||
userJoinTimes.delete(userId);
|
||||
}
|
||||
|
||||
// For every user, channelId and oldChannelId will differ when moving channel.
|
||||
// Only for the local user, channelId and oldChannelId will be the same when moving channel,
|
||||
// for some ungodly reason
|
||||
let myLastChannelId: string | undefined;
|
||||
|
||||
// Allow user updates on discord first load
|
||||
let runOneTime = true;
|
||||
|
||||
migratePluginToSettings("CallTimer", "AllCallTimers", "allCallTimers");
|
||||
export default definePlugin({
|
||||
name: "CallTimer",
|
||||
description: "Adds a timer to vcs",
|
||||
authors: [Devs.Ven],
|
||||
description: "Add call timers for all users in voice channels and in the connection status.",
|
||||
authors: [EquicordDevs.MaxHerbold, Devs.D3SOX],
|
||||
managedStyle: alignedChatInputFix,
|
||||
settings,
|
||||
|
||||
isModified: true,
|
||||
startTime: 0,
|
||||
interval: void 0 as NodeJS.Timeout | undefined,
|
||||
|
||||
patches: [{
|
||||
find: "renderConnectionStatus(){",
|
||||
replacement: {
|
||||
// in renderConnectionStatus()
|
||||
match: /(lineClamp:1,children:)(\i)(?=,|}\))/,
|
||||
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||
patches: [
|
||||
{
|
||||
find: ".usernameSpeaking]",
|
||||
predicate: () => settings.store.allCallTimers,
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=user:(\i).*?)iconGroup,.{0,200}children:\[/,
|
||||
replace: "$&$self.renderTimer($1.id),",
|
||||
predicate: () => !settings.store.showWithoutHover,
|
||||
},
|
||||
{
|
||||
match: /function \i\(\)\{.+:""(?=.*?userId:(\i))/,
|
||||
replace: "$&,$self.renderTimer($1.id),",
|
||||
predicate: () => settings.store.showWithoutHover,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "renderConnectionStatus(){",
|
||||
replacement: {
|
||||
match: /(lineClamp:1,children:)(\i)(?=,|}\))/,
|
||||
replace: "$1[$2,$self.renderConnectionTimer(this.props.channel.id)]"
|
||||
}
|
||||
}
|
||||
}],
|
||||
],
|
||||
|
||||
renderTimer(channelId: string) {
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
for (const state of voiceStates) {
|
||||
const { userId, channelId, guildId } = state;
|
||||
const isMe = userId === myId;
|
||||
|
||||
if (!guildId) {
|
||||
// guildId is never undefined here
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the state does not actually has a `oldChannelId` property
|
||||
if (!("oldChannelId" in state) && !runOneTime && !settings.store.watchLargeGuilds) {
|
||||
// batch update triggered. This is ignored because it
|
||||
// is caused by opening a previously unopened guild
|
||||
continue;
|
||||
}
|
||||
|
||||
let { oldChannelId } = state;
|
||||
if (isMe && channelId !== myLastChannelId) {
|
||||
oldChannelId = myLastChannelId;
|
||||
myLastChannelId = channelId ?? undefined;
|
||||
}
|
||||
|
||||
if (channelId !== oldChannelId) {
|
||||
if (channelId) {
|
||||
// move or join
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
} else if (oldChannelId) {
|
||||
// leave
|
||||
removeUserJoinTime(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
runOneTime = false;
|
||||
},
|
||||
PASSIVE_UPDATE_V1(passiveUpdate: PassiveUpdateState) {
|
||||
if (settings.store.watchLargeGuilds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { voiceStates } = passiveUpdate;
|
||||
if (!voiceStates) {
|
||||
// if there are no users in a voice call
|
||||
return;
|
||||
}
|
||||
|
||||
// find all users that have the same guildId and if that user is not in the voiceStates, remove them from the map
|
||||
const { guildId } = passiveUpdate;
|
||||
|
||||
// check the guildId in the userJoinTimes map
|
||||
for (const [userId, data] of userJoinTimes) {
|
||||
if (data.guildId === guildId) {
|
||||
// check if the user is in the voiceStates
|
||||
const userInVoiceStates = voiceStates.find(state => state.userId === userId);
|
||||
if (!userInVoiceStates) {
|
||||
// remove the user from the map
|
||||
removeUserJoinTime(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// since we were gifted this data let's use it to update our join times
|
||||
for (const state of voiceStates) {
|
||||
const { userId, channelId } = state;
|
||||
|
||||
if (!channelId) {
|
||||
// channelId is never undefined here
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the user is in the map
|
||||
if (userJoinTimes.has(userId)) {
|
||||
// check if the user is in a channel
|
||||
if (channelId !== userJoinTimes.get(userId)?.channelId) {
|
||||
// update the user's join time
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
}
|
||||
} else {
|
||||
// user wasn't previously tracked, add the user to the map
|
||||
addUserJoinTime(userId, channelId, guildId);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
subscribeToAllGuilds() {
|
||||
// we need to subscribe to all guilds' events because otherwise we would miss updates on large guilds
|
||||
const guilds = Object.values(GuildStore.getGuilds()).map(guild => guild.id);
|
||||
const subscriptions = guilds.reduce((acc, id) => ({ ...acc, [id]: { typing: true } }), {});
|
||||
FluxDispatcher.dispatch({ type: "GUILD_SUBSCRIPTIONS_FLUSH", subscriptions });
|
||||
},
|
||||
|
||||
start() {
|
||||
if (settings.store.watchLargeGuilds) {
|
||||
this.subscribeToAllGuilds();
|
||||
}
|
||||
},
|
||||
|
||||
renderTimer(userId: string) {
|
||||
// get the user join time from the users object
|
||||
const joinTime = userJoinTimes.get(userId);
|
||||
if (!joinTime?.time) {
|
||||
// join time is unknown
|
||||
return;
|
||||
}
|
||||
if (userId === UserStore.getCurrentUser().id && !settings.store.trackSelf) {
|
||||
// don't show for self
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Timer time={joinTime.time} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
|
||||
renderConnectionTimer(channelId: string) {
|
||||
return <ErrorBoundary noop>
|
||||
<this.Timer channelId={channelId} />
|
||||
<this.ConnectionTimer channelId={channelId} />
|
||||
</ErrorBoundary>;
|
||||
},
|
||||
|
||||
Timer({ channelId }: { channelId: string; }) {
|
||||
ConnectionTimer({ channelId }: { channelId: string; }) {
|
||||
const time = useTimer({
|
||||
deps: [channelId]
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue