Skip to content

Commit cfcf720

Browse files
committed
[FEAT] AutoVoiceChannel - copy permissions from the "Join to Create" channel
1 parent eb64148 commit cfcf720

2 files changed

Lines changed: 131 additions & 75 deletions

File tree

src/avc/avc.service.spec.ts

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { AvcService } from './avc.service';
33
import { DiscordService } from '#discord/discord.service';
44
import { DatabaseService } from '#database/database.service';
5-
import { VoiceState, Guild, ChannelType, PermissionsBitField } from 'discord.js';
5+
import {
6+
VoiceState,
7+
Guild,
8+
ChannelType,
9+
PermissionsBitField,
10+
} from 'discord.js';
611
import { VoiceChannel } from '#database/entities/voice-channel.entity';
712

813
describe('AvcService', () => {
@@ -166,23 +171,31 @@ describe('AvcService', () => {
166171
expect(databaseService.voiceChannelRepository.find).toHaveBeenCalledWith({
167172
where: { guild: { id: guild.id } },
168173
});
169-
expect((service as any).removeVoiceChannelFromDatabase).toHaveBeenCalledWith(
170-
voiceChannels[0],
174+
expect(
175+
(service as any).removeVoiceChannelFromDatabase,
176+
).toHaveBeenCalledWith(voiceChannels[0]);
177+
expect((service as any).removeEmptyChildChannels).toHaveBeenCalledWith(
178+
guild,
171179
);
172-
expect((service as any).removeEmptyChildChannels).toHaveBeenCalledWith(guild);
173180
});
174181
});
175-
182+
176183
describe('createDiscordVoiceChannel', () => {
177184
it('should create a new Discord voice channel', async () => {
178185
const guild = { channels: { create: jest.fn() } } as any;
179-
const newState = { member: { user: { username: 'test' } }, channel: { position: 1, parent: {} } } as VoiceState;
186+
const newState = {
187+
member: { user: { username: 'test' } },
188+
channel: { position: 1, parent: {} },
189+
} as VoiceState;
180190
const newChannel = { id: '456', name: "test's Channel" };
181-
191+
182192
guild.channels.create.mockResolvedValue(newChannel);
183-
184-
const result = await (service as any).createDiscordVoiceChannel(guild, newState);
185-
193+
194+
const result = await (service as any).createDiscordVoiceChannel(
195+
guild,
196+
newState,
197+
);
198+
186199
expect(guild.channels.create).toHaveBeenCalledWith({
187200
type: ChannelType.GuildVoice,
188201
name: "test's Channel",
@@ -192,114 +205,153 @@ describe('AvcService', () => {
192205
expect(result).toEqual(newChannel);
193206
});
194207
});
195-
208+
196209
describe('setChannelPermissions', () => {
197210
it('should set permissions for the new voice channel', async () => {
198-
const channel = { permissionOverwrites: { set: jest.fn() } } as any;
199-
const newState = { member: { id: 'member1' }, guild: { id: 'guild1' } } as VoiceState;
200-
201-
await (service as any).setChannelPermissions(channel, newState);
202-
203-
expect(channel.permissionOverwrites.set).toHaveBeenCalledWith([
211+
const channel = {
212+
permissionOverwrites: {
213+
set: jest.fn(),
214+
edit: jest.fn(),
215+
},
216+
} as any;
217+
218+
const parentOverwrites = [
204219
{
205220
id: 'member1',
206-
allow: [
207-
PermissionsBitField.Flags.ManageChannels,
208-
PermissionsBitField.Flags.ViewChannel,
209-
PermissionsBitField.Flags.Connect,
210-
PermissionsBitField.Flags.Speak,
211-
],
221+
allow: [PermissionsBitField.Flags.ViewChannel],
222+
deny: [],
223+
type: 1,
212224
},
213225
{
214226
id: 'guild1',
215-
allow: [
216-
PermissionsBitField.Flags.ViewChannel,
217-
PermissionsBitField.Flags.Connect,
218-
PermissionsBitField.Flags.Speak,
219-
],
227+
allow: [PermissionsBitField.Flags.ViewChannel],
228+
deny: [],
229+
type: 0,
230+
},
231+
];
232+
233+
const newState = {
234+
member: { id: 'member1' },
235+
guild: { id: 'guild1' },
236+
channel: {
237+
permissionOverwrites: {
238+
cache: {
239+
map: jest.fn().mockReturnValue(parentOverwrites),
240+
},
241+
},
220242
},
221-
]);
243+
} as unknown as VoiceState;
244+
245+
await (service as any).setChannelPermissions(channel, newState);
246+
247+
// Verify parent permissions are copied
248+
expect(newState.channel.permissionOverwrites.cache.map).toHaveBeenCalled();
249+
expect(channel.permissionOverwrites.set).toHaveBeenCalledWith(parentOverwrites);
250+
251+
// Verify member-specific permissions are set
252+
expect(channel.permissionOverwrites.edit).toHaveBeenCalledWith('member1', {
253+
ViewChannel: true,
254+
Connect: true,
255+
Speak: true,
256+
ManageChannels: true,
257+
});
222258
});
223259
});
224-
260+
225261
describe('saveVoiceChannelToDatabase', () => {
226262
it('should save the new voice channel to the database', async () => {
227263
const channel = { id: '456', name: "test's Channel" } as any;
228264
const voiceChannel = { id: '123' } as VoiceChannel;
229265
const guild = { id: 'guild1' } as any;
230-
231-
await (service as any).saveVoiceChannelToDatabase(channel, voiceChannel, guild);
232-
233-
expect(databaseService.voiceChannelRepository.insert).toHaveBeenCalledWith({
266+
267+
await (service as any).saveVoiceChannelToDatabase(
268+
channel,
269+
voiceChannel,
270+
guild,
271+
);
272+
273+
expect(
274+
databaseService.voiceChannelRepository.insert,
275+
).toHaveBeenCalledWith({
234276
id: '456',
235277
name: "test's Channel",
236278
parentId: '123',
237279
guild: { id: 'guild1' },
238280
});
239281
});
240282
});
241-
283+
242284
describe('moveUserToChannel', () => {
243285
it('should move the user to the new channel', async () => {
244286
const newState = { member: { voice: { setChannel: jest.fn() } } } as any;
245287
const newChannel = { id: '456' } as any;
246-
288+
247289
await (service as any).moveUserToChannel(newState, newChannel);
248-
290+
249291
expect(newState.member.voice.setChannel).toHaveBeenCalledWith(newChannel);
250292
});
251293
});
252-
294+
253295
describe('removeVoiceChannelFromDatabase', () => {
254296
it('should remove the voice channel from the database', async () => {
255297
const voiceChannel = { id: '123' } as VoiceChannel;
256-
298+
257299
await (service as any).removeVoiceChannelFromDatabase(voiceChannel);
258-
259-
expect(databaseService.voiceChannelRepository.remove).toHaveBeenCalledWith(voiceChannel);
300+
301+
expect(
302+
databaseService.voiceChannelRepository.remove,
303+
).toHaveBeenCalledWith(voiceChannel);
260304
});
261305
});
262-
306+
263307
describe('updateChannelNameIfNeeded', () => {
264308
it('should update the channel name if it has changed', async () => {
265309
const guild = { channels: { cache: new Map() } } as any;
266310
const voiceChannel = { id: '123', name: 'oldName' } as VoiceChannel;
267311
const channel = { id: '123', name: 'newName' } as any;
268-
312+
269313
guild.channels.cache.set('123', channel);
270-
314+
271315
await (service as any).updateChannelNameIfNeeded(guild, voiceChannel);
272-
316+
273317
expect(databaseService.voiceChannelRepository.save).toHaveBeenCalledWith({
274318
...voiceChannel,
275319
name: 'newName',
276320
});
277321
});
278322
});
279-
323+
280324
describe('removeEmptyChildChannels', () => {
281325
it('should remove empty child channels from the database and Discord', async () => {
282326
const guild = { id: 'guild1', channels: { cache: new Map() } } as any;
283327
const voiceChannels = [{ id: '123', parentId: '0' }] as VoiceChannel[];
284-
const channel = { id: '123', members: { size: 0 }, delete: jest.fn() } as any;
285-
328+
const channel = {
329+
id: '123',
330+
members: { size: 0 },
331+
delete: jest.fn(),
332+
} as any;
333+
286334
guild.channels.cache.set('123', channel);
287-
jest.spyOn(databaseService.voiceChannelRepository, 'find').mockResolvedValue(voiceChannels);
288-
335+
jest
336+
.spyOn(databaseService.voiceChannelRepository, 'find')
337+
.mockResolvedValue(voiceChannels);
338+
289339
await (service as any).removeEmptyChildChannels(guild);
290-
291-
expect(databaseService.voiceChannelRepository.remove).toHaveBeenCalledWith(voiceChannels[0]);
340+
341+
expect(
342+
databaseService.voiceChannelRepository.remove,
343+
).toHaveBeenCalledWith(voiceChannels[0]);
292344
expect(channel.delete).toHaveBeenCalled();
293345
});
294346
});
295-
347+
296348
describe('updateVoiceChannelName', () => {
297349
it('should update the voice channel name in the database', async () => {
298350
const voiceChannel = { id: '123', name: 'oldName' } as VoiceChannel;
299351
const newName = 'newName';
300-
352+
301353
await (service as any).updateVoiceChannelName(voiceChannel, newName);
302-
354+
303355
expect(databaseService.voiceChannelRepository.save).toHaveBeenCalledWith({
304356
...voiceChannel,
305357
name: newName,

src/avc/avc.service.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common';
22
import { DiscordService } from '#discord/discord.service';
33
import { VoiceChannel } from '#database/entities/voice-channel.entity';
44
import { Not } from 'typeorm';
5-
import { ChannelType, PermissionsBitField, VoiceState } from 'discord.js';
5+
import { ChannelType, VoiceState } from 'discord.js';
66
import { DiscordGuild, DiscordVoiceChannel } from '#command/command.types';
77
import { DatabaseService } from '#database/database.service';
88

@@ -94,32 +94,36 @@ export class AvcService {
9494

9595
/**
9696
* Sets the permissions for the new voice channel.
97+
* Now copies the "Join to Create" channel's permissions.
9798
* @param channel The new voice channel.
9899
* @param newState The new voice state of the user.
99100
*/
100101
private async setChannelPermissions(
101102
channel: DiscordVoiceChannel,
102103
newState: VoiceState,
103104
) {
104-
await channel.permissionOverwrites.set([
105-
{
106-
id: newState.member.id,
107-
allow: [
108-
PermissionsBitField.Flags.ManageChannels,
109-
PermissionsBitField.Flags.ViewChannel,
110-
PermissionsBitField.Flags.Connect,
111-
PermissionsBitField.Flags.Speak,
112-
],
113-
},
114-
{
115-
id: newState.guild.id,
116-
allow: [
117-
PermissionsBitField.Flags.ViewChannel,
118-
PermissionsBitField.Flags.Connect,
119-
PermissionsBitField.Flags.Speak,
120-
],
121-
},
122-
]);
105+
const parentChannel = newState.channel; // This is the Join to Create channel
106+
107+
// Clone parent permission overwrites
108+
const parentOverwrites = parentChannel.permissionOverwrites.cache.map(
109+
(overwrite) => ({
110+
id: overwrite.id,
111+
allow: overwrite.allow,
112+
deny: overwrite.deny,
113+
type: overwrite.type,
114+
}),
115+
);
116+
117+
// Apply parent permissions
118+
await channel.permissionOverwrites.set(parentOverwrites);
119+
120+
// Then add or override specific user and guild permissions
121+
await channel.permissionOverwrites.edit(newState.member.id, {
122+
ViewChannel: true,
123+
Connect: true,
124+
Speak: true,
125+
ManageChannels: true,
126+
});
123127
}
124128

125129
/**

0 commit comments

Comments
 (0)