1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-09-21 18:21:36 +02:00

Merge pull request #320 from AlTech98/master

Added /guilds/:id/voice-states/ apis, VOICE_SERVER_UPDATE fix
This commit is contained in:
Flam3rboy 2021-09-04 12:18:49 +02:00 committed by GitHub
commit 3645fb0568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 125 additions and 36 deletions

View File

@ -17,11 +17,15 @@ export function isTextChannel(type: ChannelType): boolean {
switch (type) {
case ChannelType.GUILD_STORE:
case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_STAGE_VOICE:
case ChannelType.GUILD_CATEGORY:
throw new HTTPError("not a text channel", 400);
case ChannelType.DM:
case ChannelType.GROUP_DM:
case ChannelType.GUILD_NEWS:
case ChannelType.GUILD_NEWS_THREAD:
case ChannelType.GUILD_PUBLIC_THREAD:
case ChannelType.GUILD_PRIVATE_THREAD:
case ChannelType.GUILD_TEXT:
return true;
}

View File

@ -0,0 +1,15 @@
import { check } from "../../../../../util/instanceOf";
import { VoiceStateUpdateSchema } from "../../../../../schema";
import { Request, Response, Router } from "express";
import { updateVoiceState } from "../../../../../util/VoiceState";
const router = Router();
router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema;
const { guild_id, user_id } = req.params;
await updateVoiceState(body, guild_id, req.user_id, user_id)
return res.sendStatus(204);
});
export default router;

View File

@ -0,0 +1,15 @@
import { check } from "../../../../../util/instanceOf";
import { VoiceStateUpdateSchema } from "../../../../../schema";
import { Request, Response, Router } from "express";
import { updateVoiceState } from "../../../../../util/VoiceState";
const router = Router();
router.patch("/", check(VoiceStateUpdateSchema), async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema;
const { guild_id } = req.params;
await updateVoiceState(body, guild_id, req.user_id)
return res.sendStatus(204);
});
export default router;

View File

@ -92,3 +92,15 @@ export interface GuildUpdateWelcomeScreenSchema {
enabled?: boolean;
description?: string;
}
export const VoiceStateUpdateSchema = {
channel_id: String, // Snowflake
$suppress: Boolean,
$request_to_speak_timestamp: String // ISO8601 timestamp
};
export interface VoiceStateUpdateSchema {
channel_id: string; // Snowflake
suppress?: boolean;
request_to_speak_timestamp?: string // ISO8601 timestamp
}

View File

@ -0,0 +1,54 @@
import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
import { VoiceStateUpdateSchema } from "../schema";
//TODO need more testing when community guild and voice stage channel are working
export async function updateVoiceState(vsuSchema: VoiceStateUpdateSchema, guildId: string, userId: string, targetUserId?: string) {
const perms = await getPermission(userId, guildId, vsuSchema.channel_id);
/*
From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
You must have the MUTE_MEMBERS permission to unsuppress yourself. You can always suppress yourself.
You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
*/
if (targetUserId !== undefined || (vsuSchema.suppress !== undefined && !vsuSchema.suppress)) {
perms.hasThrow("MUTE_MEMBERS");
}
if (vsuSchema.request_to_speak_timestamp !== undefined && vsuSchema.request_to_speak_timestamp !== "") {
perms.hasThrow("REQUEST_TO_SPEAK")
}
if (!targetUserId) {
targetUserId = userId;
} else {
if (vsuSchema.suppress !== undefined && vsuSchema.suppress)
vsuSchema.request_to_speak_timestamp = "" //Need to check if empty string is the right value
}
//TODO assumed that empty string means clean, need to test if it's right
let voiceState
try {
voiceState = await VoiceState.findOneOrFail({
guild_id: guildId,
channel_id: vsuSchema.channel_id,
user_id: targetUserId
});
} catch (error) {
throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
}
voiceState.assign(vsuSchema);
const channel = await Channel.findOneOrFail({ guild_id: guildId, id: vsuSchema.channel_id })
if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
}
await Promise.all([
voiceState.save(),
emitEvent({
event: "VOICE_STATE_UPDATE",
data: voiceState,
guild_id: guildId
} as VoiceStateUpdateEvent)]);
return;
}

View File

@ -2,7 +2,7 @@ import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema";
import { Payload } from "../util/Constants";
import WebSocket from "../util/WebSocket";
import { check } from "./instanceOf";
import { Config, emitEvent, Member, VoiceServerUpdateEvent, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
import { Config, emitEvent, Guild, Member, Region, VoiceServerUpdateEvent, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
import { genVoiceToken } from "../util/SessionUtils";
// TODO: check if a voice server is setup
// Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
@ -11,7 +11,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
check.call(this, VoiceStateUpdateSchema, data.d);
const body = data.d as VoiceStateUpdateSchema;
let voiceState;
let voiceState: VoiceState;
try {
voiceState = await VoiceState.findOneOrFail({
where: { user_id: this.user_id }
@ -69,14 +69,21 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
//If it's null it means that we are leaving the channel and this event is not needed
if (voiceState.channel_id !== null) {
const guild = await Guild.findOne({ id: voiceState.guild_id })
const regions = Config.get().regions;
let guildRegion: Region;
if (guild && guild.region) {
guildRegion = regions.available.filter(r => (r.id === guild.region))[0]
} else {
guildRegion = regions.available.filter(r => (r.id === regions.default))[0]
}
await emitEvent({
event: "VOICE_SERVER_UPDATE",
data: {
token: voiceState.token,
guild_id: voiceState.guild_id,
endpoint: regions.available[0].endpoint, //TODO return best endpoint or default
endpoint: guildRegion.endpoint,
},
guild_id: voiceState.guild_id,
} as VoiceServerUpdateEvent);

View File

@ -16,6 +16,11 @@ export enum ChannelType {
GUILD_CATEGORY = 4, // an organizational category that contains up to 50 channels
GUILD_NEWS = 5, // a channel that users can follow and crosspost into their own server
GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord
// TODO: what are channel types between 7-9?
GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel
GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel
GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience
}
@Entity("channels")

View File

@ -15,40 +15,9 @@ try {
export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString;
type PermissionString =
| "CREATE_INSTANT_INVITE"
| "KICK_MEMBERS"
| "BAN_MEMBERS"
| "ADMINISTRATOR"
| "MANAGE_CHANNELS"
| "MANAGE_GUILD"
| "ADD_REACTIONS"
| "VIEW_AUDIT_LOG"
| "PRIORITY_SPEAKER"
| "STREAM"
| "VIEW_CHANNEL"
| "SEND_MESSAGES"
| "SEND_TTS_MESSAGES"
| "MANAGE_MESSAGES"
| "EMBED_LINKS"
| "ATTACH_FILES"
| "READ_MESSAGE_HISTORY"
| "MENTION_EVERYONE"
| "USE_EXTERNAL_EMOJIS"
| "VIEW_GUILD_INSIGHTS"
| "CONNECT"
| "SPEAK"
| "MUTE_MEMBERS"
| "DEAFEN_MEMBERS"
| "MOVE_MEMBERS"
| "USE_VAD"
| "CHANGE_NICKNAME"
| "MANAGE_NICKNAMES"
| "MANAGE_ROLES"
| "MANAGE_WEBHOOKS"
| "MANAGE_EMOJIS_AND_STICKERS";
type PermissionString = keyof typeof Permissions.FLAGS;
const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(48); // 16 free custom permission bits, and 16 for discord to add new ones
const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(48); // 16 free custom permission bits, and 11 for discord to add new ones
export class Permissions extends BitField {
cache: PermissionCache = {};
@ -85,6 +54,14 @@ export class Permissions extends BitField {
MANAGE_ROLES: BigInt(1) << BigInt(28),
MANAGE_WEBHOOKS: BigInt(1) << BigInt(29),
MANAGE_EMOJIS_AND_STICKERS: BigInt(1) << BigInt(30),
USE_APPLICATION_COMMANDS: BigInt(1) << BigInt(31),
REQUEST_TO_SPEAK: BigInt(1) << BigInt(32),
// TODO: what is permission 33?
MANAGE_THREADS: BigInt(1) << BigInt(34),
USE_PUBLIC_THREADS: BigInt(1) << BigInt(35),
USE_PRIVATE_THREADS: BigInt(1) << BigInt(36),
USE_EXTERNAL_STICKERS: BigInt(1) << BigInt(37),
/**
* CUSTOM PERMISSIONS ideas:
* - allow user to dm members