Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ public class VoiceChannelManagementService : IVoiceChannelManagementService
private readonly Dictionary<ulong, CancellationTokenSource>
_voiceChannelDeletionCheckCancellationTokenSourcesDictionary;

private readonly HashSet<ulong> _managedVoiceChannels;

public VoiceChannelManagementService(IServiceProvider serviceProvider)
{
_voiceChannelDeletionCheckCancellationTokenSourcesDictionary =
new Dictionary<ulong, CancellationTokenSource>();
_managedVoiceChannels = new HashSet<ulong>();
_discordBotConfigurationService = serviceProvider.GetRequiredService<IDiscordBotConfigurationService>();
_discordClient = serviceProvider.GetRequiredService<DiscordSocketClient>();

Expand Down Expand Up @@ -53,6 +56,7 @@ public async Task CreateVoiceChannelAsync(IGuild guild, string name, GuildGroups
}
}

_managedVoiceChannels.Add(voiceChannel.Id);
RunDeletionCheckAsync(voiceChannel.Id);
}

Expand Down Expand Up @@ -88,43 +92,79 @@ private async Task AllowOnlyRolesAsync(IGuild guild, IVoiceChannel voiceChannel,
}
}

private Task UserVoiceStateUpdatedHandleAsync(SocketUser user, SocketVoiceState originalState,
private async Task UserVoiceStateUpdatedHandleAsync(SocketUser user, SocketVoiceState originalState,
SocketVoiceState updatedState)
{
if (updatedState.VoiceChannel != null)
{
var voiceChannel = updatedState.VoiceChannel;
CancelDeletionCheckIfExists(voiceChannel.Id);

await HandleCreatorChannelJoinAsync(user, voiceChannel);
}

if (originalState.VoiceChannel != null)
{
var voiceChannel = originalState.VoiceChannel;
if (voiceChannel.ConnectedUsers.Count == 0)
if (_managedVoiceChannels.Contains(voiceChannel.Id) && voiceChannel.ConnectedUsers.Count == 0)
{
RunDeletionCheckAsync(voiceChannel.Id);
}
}
}

if (updatedState.VoiceChannel != null)
private async Task HandleCreatorChannelJoinAsync(SocketUser user, SocketVoiceChannel joinedChannel)
{
var guild = joinedChannel.Guild;
var guildConfiguration = await _discordBotConfigurationService.GetGuildConfigurationAsync(guild.Id);

if (guildConfiguration?.VoiceChannelCreatorChannelId == null ||
joinedChannel.Id != guildConfiguration.VoiceChannelCreatorChannelId.Value)
{
var voiceChannel = updatedState.VoiceChannel;
CancelDeletionCheckIfExists(voiceChannel.Id);
return;
}

if (user is not SocketGuildUser guildUser)
{
return;
}

return Task.CompletedTask;
var customName = await _discordBotConfigurationService.GetUserChannelNameAsync(guild.Id, guildUser.Id);
var channelName = !string.IsNullOrWhiteSpace(customName?.Name)
? customName.Name
: $"{guildUser.DisplayName ?? guildUser.Username}'s channel";

var newChannel = await guild.CreateVoiceChannelAsync(channelName,
channel => channel.CategoryId = joinedChannel.CategoryId);

_managedVoiceChannels.Add(newChannel.Id);

await guildUser.ModifyAsync(properties => properties.Channel = newChannel);

RunDeletionCheckAsync(newChannel.Id);
}

private async void RunDeletionCheckAsync(ulong voiceChannelId)
{
CancelDeletionCheckIfExists(voiceChannelId);

var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

_voiceChannelDeletionCheckCancellationTokenSourcesDictionary.Add(voiceChannelId, cancellationTokenSource);
_voiceChannelDeletionCheckCancellationTokenSourcesDictionary[voiceChannelId] = cancellationTokenSource;

try
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
await Task.Delay(TimeSpan.FromMinutes(3), cancellationToken);

var voiceChannel = (SocketVoiceChannel) _discordClient.GetChannel(voiceChannelId);
if (voiceChannel != null && voiceChannel.ConnectedUsers.Count == 0)
{
await voiceChannel.DeleteAsync();
_managedVoiceChannels.Remove(voiceChannelId);
}

_voiceChannelDeletionCheckCancellationTokenSourcesDictionary.Remove(voiceChannelId);
}
catch (TaskCanceledException)
{
Expand Down
39 changes: 39 additions & 0 deletions CommandManagement/CommandHandling/ChannelsManagementModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,43 @@ public async Task SetVoiceCategory([Summary("Category")] string category)
await Context.Interaction.RespondAsync(
$"Теперь голосовые каналы будут создаваться в категории **{category}**.", ephemeral: true);
}

[RequireUserPermission(ChannelPermission.ManageChannels)]
[SlashCommand("voice-creator",
"Set a voice channel that automatically creates a personal channel when a user joins it")]
[RequireContext(ContextType.Guild)]
public async Task SetVoiceCreator([Summary("Channel")] IVoiceChannel channel)
{
var guildConfiguration = await DiscordBotConfigurationService.GetGuildConfigurationAsync(Context.Guild.Id);

if (guildConfiguration == null)
{
guildConfiguration = new DiscordGuildConfiguration
{
Id = Context.Guild.Id,
VoiceChannelCreatorChannelId = channel.Id
};

await DiscordBotConfigurationService.SaveAsync(guildConfiguration);
}
else
{
guildConfiguration.VoiceChannelCreatorChannelId = channel.Id;

await DiscordBotConfigurationService.UpdateAsync(guildConfiguration);
}

await Context.Interaction.RespondAsync(
$"Теперь канал **{channel.Name}** будет создавать персональные каналы при входе.", ephemeral: true);
}

[SlashCommand("mychannelname", "Set the name of the personal channel that is created when you join the creator")]
[RequireContext(ContextType.Guild)]
public async Task SetMyChannelName([Summary("Name")] string name)
{
await DiscordBotConfigurationService.SetUserChannelNameAsync(Context.Guild.Id, Context.User.Id, name);

await Context.Interaction.RespondAsync(
$"Теперь твой персональный канал будет называться **{name}**.", ephemeral: true);
}
}
29 changes: 29 additions & 0 deletions Configuration/DiscordBotFileBasedConfigurationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,33 @@ public async Task SaveAsync(DiscordGuildConfiguration discordGuildConfiguration)
await _applicationDbContext.GuildConfigurations.AddAsync(discordGuildConfiguration);
await _applicationDbContext.SaveChangesAsync();
}

public async ValueTask<UserVoiceChannelName> GetUserChannelNameAsync(ulong guildId, ulong userId)
{
return await _applicationDbContext.UserVoiceChannelNames
.FindAsync(guildId, userId);
}

public async Task SetUserChannelNameAsync(ulong guildId, ulong userId, string name)
{
var existing = await _applicationDbContext.UserVoiceChannelNames
.FindAsync(guildId, userId);

if (existing == null)
{
await _applicationDbContext.UserVoiceChannelNames.AddAsync(new UserVoiceChannelName
{
GuildId = guildId,
UserId = userId,
Name = name
});
}
else
{
existing.Name = name;
_applicationDbContext.UserVoiceChannelNames.Update(existing);
}

await _applicationDbContext.SaveChangesAsync();
}
}
4 changes: 4 additions & 0 deletions Configuration/IDiscordBotConfigurationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ public interface IDiscordBotConfigurationService
Task UpdateAsync(DiscordGuildConfiguration discordGuildConfiguration);

Task SaveAsync(DiscordGuildConfiguration discordGuildConfiguration);

ValueTask<UserVoiceChannelName> GetUserChannelNameAsync(ulong guildId, ulong userId);

Task SetUserChannelNameAsync(ulong guildId, ulong userId, string name);
}
14 changes: 7 additions & 7 deletions DiscordChannelsBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.10.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4"/>
<PackageReference Include="Discord.Net" Version="3.20.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.8"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.8"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2"/>
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base
FROM mcr.microsoft.com/dotnet/runtime:10.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["DiscordChannelsBot.csproj", "./"]
RUN dotnet restore "DiscordChannelsBot.csproj"
Expand Down
10 changes: 10 additions & 0 deletions Models/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,14 @@ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : ba
}

public DbSet<DiscordGuildConfiguration> GuildConfigurations { get; set; }

public DbSet<UserVoiceChannelName> UserVoiceChannelNames { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<UserVoiceChannelName>()
.HasKey(name => new {name.GuildId, name.UserId});
}
}
2 changes: 2 additions & 0 deletions Models/DiscordGuildConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ public class DiscordGuildConfiguration
public ulong Id { get; init; }

public string VoiceChannelCreationCategory { get; set; }

public ulong? VoiceChannelCreatorChannelId { get; set; }
}
10 changes: 10 additions & 0 deletions Models/UserVoiceChannelName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DiscordChannelsBot.Models;

public class UserVoiceChannelName
{
public ulong GuildId { get; init; }

public ulong UserId { get; init; }

public string Name { get; set; }
}
3 changes: 2 additions & 1 deletion Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ private static void ConfigureServices(HostBuilderContext context, IServiceCollec
GatewayIntents = GatewayIntents.All
}))
// .AddSingleton<CommandService>()
.AddSingleton<InteractionService>()
.AddSingleton<InteractionService>(provider =>
new InteractionService(provider.GetRequiredService<DiscordSocketClient>()))
.AddSingleton<IDiscordBotConfigurationService, DiscordBotFileBasedConfigurationService>()
.AddSingleton<ICommandHandlingService, CommandHandlingService>()
.AddSingleton<IVoiceChannelManagementService, VoiceChannelManagementService>()
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.0",
"version": "10.0.0",
"rollForward": "latestMajor",
"allowPrerelease": false
}
Expand Down