From 5195317cc472991009490e8ec7a7ac73c57e012a Mon Sep 17 00:00:00 2001 From: Eazvy <57739965+Eazvy@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:39:49 -0400 Subject: [PATCH] add (noOnboarding) / update (randomVoice) (#242) * Update index.tsx * add noOnboarding, update (randomVoice) / autoStream auto-completes onboarding, upon joining a server. update (randomVoice) to have autoStream and leave when vc is empty * Silent Fail --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/noOnboarding/index.tsx | 56 +++++++++++ src/equicordplugins/randomVoice/index.tsx | 105 ++++++++++++++++++--- 2 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/equicordplugins/noOnboarding/index.tsx diff --git a/src/equicordplugins/noOnboarding/index.tsx b/src/equicordplugins/noOnboarding/index.tsx new file mode 100644 index 00000000..dc29f23e --- /dev/null +++ b/src/equicordplugins/noOnboarding/index.tsx @@ -0,0 +1,56 @@ +import definePlugin from "@utils/types"; +import { Devs, EquicordDevs } from "@utils/constants"; +import { RestAPI } from "@webpack/common"; + +export default definePlugin({ + name: "NoOnboarding", + description: "Bypasses Discord's onboarding process for quicker server entry.", + authors: [EquicordDevs.omaw, Devs.Glitch], + patches: [ + { + find: ",acceptInvite(", + replacement: { + match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!=.+?;/, + replace: (m, guildId) => `${m}$self.bypassOnboard(${guildId});` + } + }, + { + find: "{joinGuild:", + replacement: { + match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/, + replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.bypassOnboard(${guildId});` + } + } + ], + bypassOnboard(guild_id: string) { + RestAPI.get({ url: `/guilds/${guild_id}/onboarding` }).then(res => { + const data = res.body; + if (!data?.prompts?.length) return; + + const now = Math.floor(Date.now() / 1000); + const prompts_seen: Record = {}; + const responses_seen: Record = {}; + const responses: string[] = []; + + for (const prompt of data.prompts) { + const options = prompt.options || []; + if (!options.length) continue; + prompts_seen[prompt.id] = now; + for (const opt of options) responses_seen[opt.id] = now; + responses.push(options[options.length - 1].id); + } + + const payload = { + onboarding_responses: responses, + onboarding_prompts_seen: prompts_seen, + onboarding_responses_seen: responses_seen, + }; + + RestAPI.post({ + url: `/guilds/${guild_id}/onboarding-responses`, + body: payload + }).catch(() => {}); + }).catch(() => {}); + } + }); + diff --git a/src/equicordplugins/randomVoice/index.tsx b/src/equicordplugins/randomVoice/index.tsx index 2b5080c1..72ecae7e 100644 --- a/src/equicordplugins/randomVoice/index.tsx +++ b/src/equicordplugins/randomVoice/index.tsx @@ -11,8 +11,8 @@ import { makeRange } from "@components/PluginSettings/components"; import { debounce } from "@shared/debounce"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { ChannelStore, ContextMenuApi, GuildStore, Menu, NavigationRouter, PermissionStore, React, SelectedChannelStore, Toasts, UserStore } from "@webpack/common"; +import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy, findByCode, findByProps } from "@webpack"; +import { ChannelStore, ContextMenuApi, GuildStore, IconUtils, Menu, ChannelRouter, PermissionStore, React, SelectedChannelStore, PermissionsBits, Toasts, UserStore } from "@webpack/common"; import style from "./styles.css?managed"; @@ -32,6 +32,7 @@ const valueOperation = [ const CONNECT = 1n << 20n; const SPEAK = 1n << 21n; const STREAM = 1n << 9n; +const VIDEO = 1 << 21; const settings = definePluginSettings({ UserAmountOperation: { @@ -85,6 +86,11 @@ const settings = definePluginSettings({ description: "Automatically turns on camera", default: false, }, + autoStream: { + type: OptionType.BOOLEAN, + description: "Automatically turns on stream", + default: false, + }, selfMute: { type: OptionType.BOOLEAN, description: "Automatically mutes your mic when joining voice-channel.", @@ -95,6 +101,11 @@ const settings = definePluginSettings({ description: "Automatically deafems your mic when joining voice-channel.", default: false, }, + leaveEmpty: { + type: OptionType.BOOLEAN, + description: "Finds a random-call, when the voice chat is empty.", + default: false, + }, avoidStages: { type: OptionType.BOOLEAN, description: "Avoids joining stage voice-channels.", @@ -137,11 +148,25 @@ const settings = definePluginSettings({ }, }); +interface VoiceState { + userId: string; + channelId?: string; + oldChannelId?: string; + deaf: boolean; + mute: boolean; + selfDeaf: boolean; + selfMute: boolean; + selfStream: boolean; + selfVideo: boolean; + sessionId: string; + suppress: boolean; + requestToSpeakTimestamp: string | null; +} export default definePlugin({ name: "RandomVoice", description: "Adds a Button near the Mute button to join a random voice call.", - authors: [EquicordDevs.xijexo, EquicordDevs.omaw], + authors: [EquicordDevs.xijexo, EquicordDevs.omaw, EquicordDevs.thororen], patches: [ { find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", @@ -151,6 +176,22 @@ export default definePlugin({ } } ], + flux: { + VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[] }) { + const currentUserId = UserStore.getCurrentUser().id; + const myChannelId = VoiceStateStore.getVoiceStateForUser(currentUserId)?.channelId; + if (!myChannelId || !settings.store.leaveEmpty) return; + + const voiceStatesMap = VoiceStateStore.getVoiceStates() as Record; + const othersInChannel = Object.values(voiceStatesMap).filter(vs => + vs.channelId === myChannelId && vs.userId !== currentUserId + ); + + if (othersInChannel.length === 0) { + randomVoice() + } + }, + }, start() { enableStyle(style); }, @@ -194,8 +235,7 @@ function ContextMenu() { }); ServerList = Array.from(new Set(ServerList)); - const Servers = ServerList.map(server => GuildStore.getGuild(server)).filter(guild => guild !== null); - + const Servers = ServerList.map(server => GuildStore.getGuild(server)).filter(guild => guild && guild.id); const [servers, setServers] = React.useState(settings.store.Servers); const [SpacesLeftOperation, setSpacesLeftOperation] = React.useState(settings.store.spacesLeftOperation); const [userAmount, setuserAmount] = React.useState(settings.store.UserAmountOperation); @@ -204,12 +244,13 @@ function ContextMenu() { const [stage, setStage] = React.useState(settings.store.avoidStages); const [afk, setAfk] = React.useState(settings.store.avoidAfk); const [camera, setCamera] = React.useState(settings.store.autoCamera); + const [stream, setStream] = React.useState(settings.store.autoStream); + const [empty, setEmpty] = React.useState(settings.store.leaveEmpty); const [muteself, setSelfMute] = React.useState(settings.store.selfMute); const [deafenself, setSelfDeafen] = React.useState(settings.store.selfDeafen); const [mute, setMute] = React.useState(settings.store.mute); const [deafen, setDeafen] = React.useState(settings.store.deafen); const [video, setVideo] = React.useState(settings.store.video); - const [stream, setStream] = React.useState(settings.store.stream); const [state, setState] = React.useState(settings.store.includeStates); const [notstate, avoidState] = React.useState(settings.store.avoidStates); @@ -219,6 +260,8 @@ function ContextMenu() { onClose={() => { }} aria-label="Voice state modifier" > + + { setVideo(!video); settings.store.video = !video; @@ -545,7 +588,7 @@ function ContextMenu() { { }} > <> @@ -577,6 +620,24 @@ function ContextMenu() { settings.store.autoCamera = !camera; }} checked={camera} /> + { + setStream(!stream); + settings.store.autoStream = !stream; + }} + checked={stream} /> + { + setEmpty(!empty); + settings.store.leaveEmpty = !empty; + }} + checked={empty} /> @@ -704,15 +765,35 @@ function getChannels() { function JoinVc(channelID) { const channel = ChannelStore.getChannel(channelID); - const channel_link = `/channels/${channel.guild_id}/${channel.id}`; ChannelActions.selectVoiceChannel(channelID); - if (settings.store.autoNavigate) NavigationRouter.transitionTo(channel_link); - if (settings.store.autoCamera && PermissionStore.can(STREAM, channel)) autoCamera(); - if (settings.store.autoCamera && PermissionStore.can(STREAM, channel)) autoCamera(); + if (settings.store.autoNavigate) ChannelRouter.transitionToChannel(channel.id); + if (settings.store.autoCamera && PermissionStore.can(VIDEO, channel)) autoCamera(); + if (settings.store.autoStream && PermissionStore.can(STREAM, channel)) autoStream(); if (settings.store.selfMute && !MediaEngineStore.isSelfMute() && SelectedChannelStore.getVoiceChannelId()) toggleSelfMute(); if (settings.store.selfDeafen && !MediaEngineStore.isSelfDeaf() && SelectedChannelStore.getVoiceChannelId()) toggleSelfDeaf(); } +async function autoStream() { + const startStream = findByCode('type:"STREAM_START"'); + const mediaEngine = findByProps("getMediaEngine").getMediaEngine(); + const getDesktopSources = findByCode("desktop sources"); + const selected = SelectedChannelStore.getVoiceChannelId(); + if (!selected) return; + const channel = ChannelStore.getChannel(selected); + const sources = await getDesktopSources(mediaEngine, ["screen"], null); + if (!sources || sources.length === 0) return; + const source = sources[0]; + if (channel.type === 13 || !PermissionStore.can(PermissionsBits.STREAM, channel)) return; + startStream(channel.guild_id, selected, { + "pid": null, + "sourceId": source.id, + "sourceName": source.name, + "audioSourceId": null, + "sound": true, + "previewDisabled": false + }); +} + function autoCamera() { const checkExist = setInterval(() => { const cameraOFF = document.querySelector('[aria-label="Turn off Camera" i]') as HTMLButtonElement;