mirror of
https://github.com/Equicord/Equicord.git
synced 2025-07-01 17:34:24 -04:00
This commit is contained in:
parent
f249c0c7be
commit
dcf322b832
9 changed files with 326 additions and 25 deletions
|
@ -11,7 +11,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Forms, Menu, React } from "@webpack/common";
|
||||
import { Forms, Menu, React, VoiceStateStore } from "@webpack/common";
|
||||
import { VoiceState } from "@webpack/types";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
|
@ -29,7 +29,6 @@ interface UserContextProps {
|
|||
let followedUserInfo: TFollowedUserInfo = null;
|
||||
|
||||
const voiceChannelAction = findByPropsLazy("selectVoiceChannel");
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
const UserStore = findStoreLazy("UserStore");
|
||||
const RelationshipStore = findStoreLazy("RelationshipStore");
|
||||
|
||||
|
|
314
src/equicordplugins/orbolayBridge/index.tsx
Normal file
314
src/equicordplugins/orbolayBridge/index.tsx
Normal file
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 OpenAsar
|
||||
*
|
||||
* 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 { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, FluxDispatcher, GuildMemberStore, Toasts, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
|
||||
interface ChannelState {
|
||||
userId: string;
|
||||
channelId: string;
|
||||
deaf: boolean;
|
||||
mute: boolean;
|
||||
stream: boolean;
|
||||
selfDeaf: boolean;
|
||||
selfMute: boolean;
|
||||
selfStream: boolean;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
port: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Port to connect to",
|
||||
default: 6888,
|
||||
restartNeeded: true
|
||||
},
|
||||
messageAlignment: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Alignment of messages in the overlay",
|
||||
options: [
|
||||
{ label: "Top left", value: "topleft", default: true },
|
||||
{ label: "Top right", value: "topright" },
|
||||
{ label: "Bottom left", value: "bottomleft" },
|
||||
{ label: "Bottom right", value: "bottomright" },
|
||||
],
|
||||
default: "topright",
|
||||
restartNeeded: true
|
||||
},
|
||||
userAlignment: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Alignment of users in the overlay",
|
||||
options: [
|
||||
{ label: "Top left", value: "topleft", default: true },
|
||||
{ label: "Top right", value: "topright" },
|
||||
{ label: "Bottom left", value: "bottomleft" },
|
||||
{ label: "Bottom right", value: "bottomright" },
|
||||
],
|
||||
default: "topleft",
|
||||
restartNeeded: true
|
||||
},
|
||||
voiceSemitransparent: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Make voice channel members transparent",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
messagesSemitransparent: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Make message notifications transparent",
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
});
|
||||
|
||||
let ws: WebSocket | null = null;
|
||||
let currentChannel = null;
|
||||
|
||||
const waitForPopulate = async fn => {
|
||||
while (true) {
|
||||
const result = await fn();
|
||||
if (result) return result;
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
};
|
||||
|
||||
function stateToPayload(guildId: string, state: ChannelState) {
|
||||
const user = UserStore.getUser(state.userId);
|
||||
const nickname = GuildMemberStore.getNick(guildId, state.userId);
|
||||
return {
|
||||
userId: state.userId,
|
||||
username: nickname || (user as any).globalName || user.username,
|
||||
avatarUrl: user.avatar,
|
||||
channelId: state.channelId,
|
||||
deaf: state.deaf || state.selfDeaf,
|
||||
mute: state.mute || state.selfMute,
|
||||
streaming: state.selfStream,
|
||||
speaking: false,
|
||||
};
|
||||
}
|
||||
|
||||
const incoming = payload => {
|
||||
switch (payload.cmd) {
|
||||
case "TOGGLE_MUTE":
|
||||
FluxDispatcher.dispatch({
|
||||
type: "AUDIO_TOGGLE_SELF_MUTE",
|
||||
syncRemote: true,
|
||||
playSoundEffect: true,
|
||||
context: "default"
|
||||
});
|
||||
break;
|
||||
case "TOGGLE_DEAF":
|
||||
FluxDispatcher.dispatch({
|
||||
type: "AUDIO_TOGGLE_SELF_DEAF",
|
||||
syncRemote: true,
|
||||
playSoundEffect: true,
|
||||
context: "default"
|
||||
});
|
||||
break;
|
||||
case "DISCONNECT":
|
||||
FluxDispatcher.dispatch({
|
||||
type: "VOICE_CHANNEL_SELECT",
|
||||
channelId: null
|
||||
});
|
||||
break;
|
||||
case "STOP_STREAM": {
|
||||
const userId = UserStore.getCurrentUser()?.id;
|
||||
const voiceState = VoiceStateStore.getVoiceStateForUser(userId);
|
||||
const channel = ChannelStore.getChannel(voiceState.channelId);
|
||||
|
||||
// If any of these are null, we can't do anything
|
||||
if (!userId || !voiceState || !channel) return;
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "STREAM_STOP",
|
||||
streamKey: `guild:${channel.guild_id}:${voiceState.channelId}:${userId}`,
|
||||
appContext: "APP"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSpeaking = dispatch => {
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "VOICE_STATE_UPDATE",
|
||||
state: {
|
||||
userId: dispatch.userId,
|
||||
speaking: dispatch.speakingFlags === 1,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleMessageNotification = dispatch => {
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "MESSAGE_NOTIFICATION",
|
||||
message: {
|
||||
title: dispatch.title,
|
||||
body: dispatch.body,
|
||||
icon: dispatch.icon,
|
||||
channelId: dispatch.channelId,
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleVoiceStateUpdates = async dispatch => {
|
||||
// Ensure we are in the channel that the update is for
|
||||
const { id } = UserStore.getCurrentUser();
|
||||
|
||||
for (const state of dispatch.voiceStates) {
|
||||
const ourState = state.userId === id;
|
||||
const { guildId } = state;
|
||||
|
||||
if (ourState) {
|
||||
if (state.channelId && state.channelId !== currentChannel) {
|
||||
const voiceStates = await waitForPopulate(() =>
|
||||
VoiceStateStore?.getVoiceStatesForChannel(state.channelId)
|
||||
);
|
||||
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "CHANNEL_JOINED",
|
||||
states: Object.values(voiceStates).map(s => stateToPayload(guildId, s as ChannelState)),
|
||||
})
|
||||
);
|
||||
|
||||
currentChannel = state.channelId;
|
||||
|
||||
break;
|
||||
} else if (!state.channelId) {
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "CHANNEL_LEFT",
|
||||
})
|
||||
);
|
||||
|
||||
currentChannel = null;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is for the channel we are in, send a VOICE_STATE_UPDATE
|
||||
if (
|
||||
!!currentChannel &&
|
||||
(state.channelId === currentChannel ||
|
||||
state.oldChannelId === currentChannel)
|
||||
) {
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "VOICE_STATE_UPDATE",
|
||||
state: stateToPayload(guildId, state as ChannelState),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createWebsocket = () => {
|
||||
console.log("Attempting to connect to Orbolay server");
|
||||
|
||||
// First ensure old connection is closed
|
||||
if (ws?.close) ws.close();
|
||||
|
||||
setTimeout(() => {
|
||||
// If the ws is not ready, kill it and log
|
||||
if (ws?.readyState !== WebSocket.OPEN) {
|
||||
Toasts.show({
|
||||
message: "Orbolay websocket could not connect. Is it running?",
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
});
|
||||
ws = null;
|
||||
return;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
ws = new WebSocket("ws://127.0.0.1:" + settings.store.port);
|
||||
ws.onerror = e => {
|
||||
ws?.close?.();
|
||||
ws = null;
|
||||
throw e;
|
||||
};
|
||||
ws.onmessage = e => {
|
||||
incoming(JSON.parse(e.data));
|
||||
};
|
||||
ws.onclose = () => {
|
||||
ws = null;
|
||||
};
|
||||
ws.onopen = async () => {
|
||||
Toasts.show({
|
||||
message: "Connected to Orbolay server",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
});
|
||||
|
||||
// Send over the config
|
||||
const config = {
|
||||
...settings.store,
|
||||
userId: null,
|
||||
};
|
||||
|
||||
// Ensure we track the current user id
|
||||
config.userId = await waitForPopulate(() => UserStore.getCurrentUser().id);
|
||||
|
||||
ws?.send(JSON.stringify({ cmd: "REGISTER_CONFIG", ...config }));
|
||||
|
||||
// Send initial channel joined (if the user is in a channel)
|
||||
const userVoiceState = VoiceStateStore.getVoiceStateForUser(config.userId);
|
||||
|
||||
if (!userVoiceState) return;
|
||||
|
||||
const channelState = VoiceStateStore.getVoiceStatesForChannel(userVoiceState.channelId);
|
||||
const { guildId } = userVoiceState;
|
||||
|
||||
ws?.send(
|
||||
JSON.stringify({
|
||||
cmd: "CHANNEL_JOINED",
|
||||
states: Object.values(channelState).map(s => stateToPayload(guildId, s as ChannelState)),
|
||||
})
|
||||
);
|
||||
|
||||
currentChannel = userVoiceState.channelId;
|
||||
};
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Orbolay Bridge",
|
||||
description: "Bridge plugin to connect Orbolay to Discord",
|
||||
authors: [EquicordDevs.SpikeHD],
|
||||
settings,
|
||||
start() {
|
||||
createWebsocket();
|
||||
|
||||
FluxDispatcher.subscribe("SPEAKING", handleSpeaking);
|
||||
FluxDispatcher.subscribe("VOICE_STATE_UPDATES", handleVoiceStateUpdates);
|
||||
FluxDispatcher.subscribe("RPC_NOTIFICATION_CREATE", handleMessageNotification);
|
||||
},
|
||||
stop() {
|
||||
ws?.close();
|
||||
ws = null;
|
||||
|
||||
FluxDispatcher.unsubscribe("SPEAKING", handleSpeaking);
|
||||
FluxDispatcher.unsubscribe("VOICE_STATE_UPDATES", handleVoiceStateUpdates);
|
||||
FluxDispatcher.unsubscribe("RPC_NOTIFICATION_CREATE", handleMessageNotification);
|
||||
}
|
||||
});
|
|
@ -12,13 +12,12 @@ import { debounce } from "@shared/debounce";
|
|||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCode, findByProps, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, ContextMenuApi, GuildStore, Menu, PermissionsBits, PermissionStore, React, SelectedChannelStore, Toasts, UserStore } from "@webpack/common";
|
||||
import { ChannelRouter, ChannelStore, ContextMenuApi, GuildStore, Menu, PermissionsBits, PermissionStore, React, SelectedChannelStore, Toasts, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
|
||||
import style from "./styles.css?managed";
|
||||
|
||||
const ChatVoiceIcon = findComponentByCodeLazy("0l1.8-1.8c.17");
|
||||
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||
const ChannelActions = findByPropsLazy("selectChannel", "selectVoiceChannel");
|
||||
const { toggleSelfMute } = findByPropsLazy("toggleSelfMute");
|
||||
|
|
|
@ -11,7 +11,6 @@ import { wordsToTitle } from "@utils/text";
|
|||
import definePlugin, {
|
||||
OptionType,
|
||||
} from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import {
|
||||
Button,
|
||||
ChannelStore,
|
||||
|
@ -22,6 +21,7 @@ import {
|
|||
SelectedGuildStore,
|
||||
useMemo,
|
||||
UserStore,
|
||||
VoiceStateStore,
|
||||
} from "@webpack/common";
|
||||
|
||||
// Create an in-memory cache (temporary, lost on restart)
|
||||
|
@ -77,11 +77,6 @@ interface VoiceState {
|
|||
selfMute: boolean;
|
||||
}
|
||||
|
||||
const VoiceStateStore = findByPropsLazy(
|
||||
"getVoiceStatesForChannel",
|
||||
"getCurrentClientVoiceChannelId"
|
||||
);
|
||||
|
||||
// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying
|
||||
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
||||
// not say the second mute, which would lead you to believe they're unmuted
|
||||
|
|
|
@ -9,12 +9,9 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { GuildChannelStore, Menu, React, RestAPI, UserStore } from "@webpack/common";
|
||||
import { GuildChannelStore, Menu, React, RestAPI, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
import type { Channel } from "discord-types/general";
|
||||
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
|
||||
async function runSequential<T>(promises: Promise<T>[]): Promise<T[]> {
|
||||
const results: T[] = [];
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { humanFriendlyJoin } from "@utils/text";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { ChannelStore, FluxDispatcher, MessageActions, MessageStore, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { ChannelStore, FluxDispatcher, MessageActions, MessageStore, RelationshipStore, SelectedChannelStore, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
import { Message, User } from "discord-types/general";
|
||||
|
||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||
const SortedVoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
friendDirectMessages: {
|
||||
|
@ -127,7 +126,7 @@ export default definePlugin({
|
|||
const selfInChannel = SelectedChannelStore.getVoiceChannelId() === channelId;
|
||||
let memberListContent = "";
|
||||
if (settings.store.friendDirectMessagesShowMembers || settings.store.friendDirectMessagesShowMemberCount) {
|
||||
const voiceState = SortedVoiceStateStore.getVoiceStatesForChannel(channelId);
|
||||
const voiceState = VoiceStateStore.getVoiceStatesForChannel(channelId);
|
||||
const sortedVoiceStates: User[] = Object.values(voiceState as { [key: string]: VoiceState; })
|
||||
.filter((voiceState: VoiceState) => { voiceState.user && voiceState.user.id !== userId; })
|
||||
.map((voiceState: VoiceState) => voiceState.user);
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { classes } from "@utils/misc";
|
||||
import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores, VoiceStateStore } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
const cl = classNameFactory("vc-uvs-");
|
||||
|
@ -18,7 +18,6 @@ const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
|
|||
useChannelName: filters.byCode("()=>null==")
|
||||
});
|
||||
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
|
||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||
const Avatar = findComponentByCodeLazy(".status)/2):0");
|
||||
|
|
|
@ -22,8 +22,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { Margins } from "@utils/margins";
|
||||
import { wordsToTitle } from "@utils/text";
|
||||
import definePlugin, { ReporterTestable } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
|
||||
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore, VoiceStateStore } from "@webpack/common";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
import { getCurrentVoice, settings } from "./settings";
|
||||
|
@ -38,8 +37,6 @@ interface VoiceState {
|
|||
selfMute: boolean;
|
||||
}
|
||||
|
||||
const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId");
|
||||
|
||||
// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying
|
||||
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
||||
// not say the second mute, which would lead you to believe they're unmuted
|
||||
|
|
|
@ -38,6 +38,7 @@ export let PermissionStore: GenericStore;
|
|||
export let GuildChannelStore: GenericStore;
|
||||
export let ReadStateStore: GenericStore;
|
||||
export let PresenceStore: GenericStore;
|
||||
export let VoiceStateStore: GenericStore;
|
||||
|
||||
export let GuildStore: t.GuildStore;
|
||||
export let UserStore: Stores.UserStore & t.FluxStore;
|
||||
|
@ -90,3 +91,4 @@ waitForStore("ThemeStore", m => {
|
|||
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
|
||||
Vencord.QuickCss.initQuickCssThemeStore();
|
||||
});
|
||||
waitForStore("VoiceStateStore", m => VoiceStateStore = m);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue