Skip to content

Commit

Permalink
Merge pull request #10 from DevSubmarine/dev
Browse files Browse the repository at this point in the history
Release v1.3.3
  • Loading branch information
guraysenova authored Aug 11, 2023
2 parents 80c34b9 + cbb9a2e commit 5775716
Show file tree
Hide file tree
Showing 24 changed files with 567 additions and 299 deletions.
2 changes: 1 addition & 1 deletion DiscordBot/Client/Extensions/DIscordUserExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static string GetSafeAvatarUrl(this IUser user, ImageFormat format = Imag
=> user.GetAvatarUrl(format, size) ?? user.GetDefaultAvatarUrl();

public static string GetUsernameWithDiscriminator(this IUser user)
=> $"{user.Username}#{user.Discriminator}";
=> user.DiscriminatorValue == 0 ? user.Username : $"{user.Username}#{user.Discriminator}";

public static string GetName(this IUser user)
{
Expand Down
21 changes: 21 additions & 0 deletions DiscordBot/Client/HostedDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class HostedDiscordClient : IHostedDiscordClient, IHostedService, IDispos
private readonly IDisposable _optionsChangeHandle;
private DiscordSocketClient _client;
private bool _started = false;
private TaskCompletionSource<object> _connectionTcs;

public HostedDiscordClient(IOptionsMonitor<DiscordOptions> discordOptions, ILogger<HostedDiscordClient> log)
{
Expand All @@ -23,8 +24,10 @@ public HostedDiscordClient(IOptionsMonitor<DiscordOptions> discordOptions, ILogg
clientConfig.LogLevel = LogSeverity.Verbose;
clientConfig.LogGatewayIntentWarnings = false;
clientConfig.GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent | GatewayIntents.GuildMembers;
this._connectionTcs = new TaskCompletionSource<object>();
this._client = new DiscordSocketClient(clientConfig);
this._client.Log += this.OnClientLog;
this._client.Ready += this.OnClientReady;

this._optionsChangeHandle = this._discordOptions.OnChange(async _ =>
{
Expand All @@ -37,6 +40,12 @@ public HostedDiscordClient(IOptionsMonitor<DiscordOptions> discordOptions, ILogg
});
}

private Task OnClientReady()
{
this._connectionTcs.TrySetResult(null);
return Task.CompletedTask;
}

public async Task StartClientAsync()
{
await this._client.LoginAsync(TokenType.Bot, this._discordOptions.CurrentValue.BotToken).ConfigureAwait(false);
Expand All @@ -45,12 +54,22 @@ public async Task StartClientAsync()

public async Task StopClientAsync()
{
this._connectionTcs?.TrySetCanceled();
this._connectionTcs = new TaskCompletionSource<object>();

if (this._client.LoginState == LoginState.LoggedIn || this._client.LoginState == LoginState.LoggingIn)
await this._client.LogoutAsync().ConfigureAwait(false);
if (this._client.ConnectionState == ConnectionState.Connected || this._client.ConnectionState == ConnectionState.Connecting)
await this._client.StopAsync().ConfigureAwait(false);
}

public Task WaitForConnectionAsync(CancellationToken cancellationToken = default)
{
if (this._connectionTcs.Task.IsCompleted)
return Task.CompletedTask;
return Task.WhenAny(this._connectionTcs.Task, Task.Delay(-1, cancellationToken));
}

private Task OnClientLog(LogMessage message)
{
this._log.Log(message);
Expand Down Expand Up @@ -78,6 +97,8 @@ public static implicit operator DiscordSocketClient(HostedDiscordClient client)
public void Dispose()
{
try { this._client.Log -= this.OnClientLog; } catch { }
try { this._client.Ready -= this.OnClientReady; } catch { }
try { this._connectionTcs?.TrySetCanceled(); } catch { }
try { this._client?.Dispose(); } catch { }
try { this._optionsChangeHandle?.Dispose(); } catch { }
}
Expand Down
2 changes: 2 additions & 0 deletions DiscordBot/Client/IHostedDiscordClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public interface IHostedDiscordClient
{
IDiscordClient Client { get; }

Task WaitForConnectionAsync(CancellationToken cancellationToken = default);

Task StartClientAsync();
Task StopClientAsync();
}
Expand Down
16 changes: 8 additions & 8 deletions DiscordBot/DiscordBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<RepositoryUrl>https://github.com/DevSubmarine/DiscordBot</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>bot; discord; devsub; devsubmarine</PackageTags>
<Version>1.3.2</Version>
<Version>1.4.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down Expand Up @@ -73,22 +73,22 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.8.1" />
<PackageReference Include="Discord.Net" Version="3.11.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.16.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="MongoDB.Driver" Version="2.18.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Datadog.Logs" Version="0.3.6" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Datadog.Logs" Version="0.5.2" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="TehGM.Utilities.Randomization" Version="0.1.0" />
<PackageReference Include="TehGM.Utilities.Randomization" Version="0.2.2" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion DiscordBot/Features/AboutCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task CmdAboutAsync(
IGuildUser authorGuildUser = base.Context.Guild?.GetUser(authorUser.Id);

string botName = guildUser?.Nickname ?? user.Username;
string authorName = authorGuildUser?.Mention ?? user?.GetUsernameWithDiscriminator() ?? "TehGM";
string authorName = authorGuildUser?.Mention ?? authorUser?.GetUsernameWithDiscriminator() ?? "TehGM";

StringBuilder features = new StringBuilder();
this.AddFeatureInfo(features, "Colour Roles", $"You can change the colour of your nickname using `/colour` commands! {ResponseEmoji.ParrotParty}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace DevSubmarine.DiscordBot.Birthdays.Services
{
public class UserBirthdayAutopostService : IHostedService, IDisposable
{
private readonly DiscordSocketClient _client;
private readonly IHostedDiscordClient _client;
private readonly IUserBirthdaysProvider _provider;
private readonly IUserBirthdayEmbedBuilder _embedBuilder;
private readonly ILogger _log;
Expand All @@ -15,7 +15,7 @@ public class UserBirthdayAutopostService : IHostedService, IDisposable

private UserBirthdaysOptions Options => this._options.CurrentValue;

public UserBirthdayAutopostService(IUserBirthdaysProvider provider, IUserBirthdayEmbedBuilder embedBuilder, DiscordSocketClient client,
public UserBirthdayAutopostService(IUserBirthdaysProvider provider, IUserBirthdayEmbedBuilder embedBuilder, IHostedDiscordClient client,
ILogger<UserBirthdayAutopostService> log, IOptionsMonitor<UserBirthdaysOptions> options)
{
this._provider = provider;
Expand All @@ -29,11 +29,7 @@ private async Task ScannerLoopAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
while (this._client.ConnectionState != ConnectionState.Connected)
{
this._log.LogTrace("Client not connected, waiting");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
}
await this._client.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);

if (this.Options.AutoPostChannelID != null)
{
Expand All @@ -42,7 +38,7 @@ private async Task ScannerLoopAsync(CancellationToken cancellationToken)
if (embed != null)
{
this._log.LogDebug("Auto-posting upcoming birthdays");
IChannel channel = await this._client.GetChannelAsync(this.Options.AutoPostChannelID.Value, cancellationToken.ToRequestOptions()).ConfigureAwait(false);
IChannel channel = await this._client.Client.GetChannelAsync(this.Options.AutoPostChannelID.Value, CacheMode.AllowDownload, cancellationToken.ToRequestOptions()).ConfigureAwait(false);
if (channel is not ITextChannel textChannel)
{
this._log.LogError("Channel ID {ChannelID} is not a valid text channel", channel.Id);
Expand Down
7 changes: 7 additions & 0 deletions DiscordBot/Features/BlogsManagement/BlogChannelProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace DevSubmarine.DiscordBot.BlogsManagement
{
public class BlogChannelProperties
{
public bool NSFW { get; init; } = false;
}
}
111 changes: 98 additions & 13 deletions DiscordBot/Features/BlogsManagement/Commands/BlogManagementCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ public BlogManagementCommands(IBlogChannelManager manager, IBlogChannelNameConve
}

[SlashCommand("create", "Creates a blog channel for user")]
public async Task CmdClearAsync(
[Summary("User", "Which user to create channel for; can only be used by administrators")] IGuildUser user = null)
public async Task CmdCreateAsync(
[Summary("User", "Which user to create channel for; can only be used by administrators")] IGuildUser user = null,
[Summary("NSFW", "Should the channel be marked as NSFW?")] bool nsfw = false)
{
await base.DeferAsync(options: base.GetRequestOptions()).ConfigureAwait(false);

// creating channel for other user should only be possible for admins
IGuildUser callerUser = await base.Context.Guild.GetGuildUserAsync(base.Context.User.Id, base.CancellationToken).ConfigureAwait(false);
if (user != null)
{
if (!callerUser.IsOwner() && !callerUser.GuildPermissions.Administrator)
if (!CreatingForSelf() && !callerUser.IsOwner() && !callerUser.GuildPermissions.Administrator)
{
await base.ModifyOriginalResponseAsync(msg => msg.Content = $"{ResponseEmoji.Failure} You have no permissions to create blogs for other users.");
return;
Expand All @@ -48,14 +49,14 @@ public async Task CmdClearAsync(
if (!this._nameConverter.TryConvertUsername(user.Username, out string channelName))
{
string usernameMention = CreatingForSelf() ? "Your username" : $"{user.Mention}'s username";
await RespondFailureAsync($"{ResponseEmoji.Failure} {usernameMention} contains invalid characters. {ResponseEmoji.FeelsDumbMan}").ConfigureAwait(false);
await this.RespondFailureAsync($"{ResponseEmoji.Failure} {usernameMention} contains invalid characters. {ResponseEmoji.FeelsDumbMan}").ConfigureAwait(false);
return;
}

IEnumerable<IGuildChannel> existingChannels = await this._manager.GetBlogChannelsAsync(channelName, base.CancellationToken).ConfigureAwait(false);
if (existingChannels.Any())
{
await RespondFailureAsync($"{ResponseEmoji.Failure} Channel {MentionUtils.MentionChannel(existingChannels.First().Id)} already exists {ResponseEmoji.FeelsBeanMan}");
await this.RespondFailureAsync($"{ResponseEmoji.Failure} Channel {MentionUtils.MentionChannel(existingChannels.First().Id)} already exists {ResponseEmoji.FeelsBeanMan}");
return;
}

Expand All @@ -64,7 +65,7 @@ public async Task CmdClearAsync(
{
string responseStart = CreatingForSelf() ? "You already have" : $"{user.Mention} already has";
string channelsMentions = string.Join(", ", userChannels.OrderBy(channel => channel.Name).Select(channel => MentionUtils.MentionChannel(channel.Id)));
await RespondFailureAsync($"{ResponseEmoji.Failure} {responseStart} access to {channelsMentions} blog channel(s). {ResponseEmoji.BlobSweatAnimated}").ConfigureAwait(false);
await this.RespondFailureAsync($"{ResponseEmoji.Failure} {responseStart} access to {channelsMentions} blog channel(s). {ResponseEmoji.BlobSweatAnimated}").ConfigureAwait(false);
return;
}

Expand All @@ -74,13 +75,18 @@ public async Task CmdClearAsync(
if (CreatingForSelf() && memberAge < this._options.MinMemberAge)
{
string[] emojis = new string[] { ResponseEmoji.FeelsBeanMan, ResponseEmoji.FeelsDumbMan, ResponseEmoji.EyesBlurry, ResponseEmoji.BlobSweatAnimated };
await RespondFailureAsync($"{ResponseEmoji.Failure} You need to be here for at least {this._options.MinMemberAge.ToDisplayString()} to create a blog channel.\nYou've been here for {memberAge.ToDisplayString()} so far. {this._randomizer.GetRandomValue(emojis)}");
await this.RespondFailureAsync($"{ResponseEmoji.Failure} You need to be here for at least {this._options.MinMemberAge.ToDisplayString()} to create a blog channel.\nYou've been here for {memberAge.ToDisplayString()} so far. {this._randomizer.GetRandomValue(emojis)}");
return;
}

try
{
IGuildChannel result = await this._manager.CreateBlogChannel(channelName, user.Id, base.CancellationToken).ConfigureAwait(false);
BlogChannelProperties props = new BlogChannelProperties()
{
NSFW = nsfw
};

IGuildChannel result = await this._manager.CreateBlogChannel(channelName, user.Id, props, base.CancellationToken).ConfigureAwait(false);
await base.ModifyOriginalResponseAsync(msg =>
{
msg.Content = null;
Expand Down Expand Up @@ -110,15 +116,94 @@ await base.ModifyOriginalResponseAsync(msg => msg.Content = $"Oops! {ResponseEmo
return;
}


bool CreatingForSelf()
=> callerUser == user;
Task RespondFailureAsync(string text)
=> base.ModifyOriginalResponseAsync(msg =>
}

[SlashCommand("update", "Updates a blog channel for user")]
public async Task CmdUpdateAsync(
[Summary("Channel", "Which channel to update; not needed if you only have 1 blog"), Autocomplete(typeof(UserBlogChannelsAutocompleteHandler))] string providedChannelID = null,
[Summary("NSFW", "Should the channel be marked as NSFW?")] bool nsfw = false)
{
await base.DeferAsync(options: base.GetRequestOptions()).ConfigureAwait(false);

if (!ulong.TryParse(providedChannelID ?? "0", out ulong channelID))
{
await this.RespondFailureAsync($"Channel with specified ID not found. {ResponseEmoji.Failure}").ConfigureAwait(false);
return;
}

IGuildUser callerUser = await base.Context.Guild.GetGuildUserAsync(base.Context.User.Id, base.CancellationToken).ConfigureAwait(false);
bool isAdmin = callerUser.IsOwner() || callerUser.GuildPermissions.Administrator;
IEnumerable<IGuildChannel> channels = isAdmin && channelID != 0
? await this._manager.GetBlogChannelsAsync(base.CancellationToken).ConfigureAwait(false)
: await this._manager.FindUserBlogChannelsAsync(callerUser.Id, base.CancellationToken).ConfigureAwait(false);
channels = channels.ToArray();

if (channelID == 0)
{
if (channels.Count() > 1)
{
msg.Content = text;
msg.AllowedMentions = AllowedMentions.None;
await this.RespondFailureAsync($"You can edit more than 1 channel - specify which one you want to edit. {ResponseEmoji.Failure}").ConfigureAwait(false);
return;
}

channelID = channels.First().Id;
}
else if (!channels.Any(c => c.Id == channelID))
{
await this.RespondFailureAsync($"You have no rights to edit this channel! {ResponseEmoji.BlobSweatAnimated}").ConfigureAwait(false);
return;
}

IGuildChannel channel = base.Context.Guild.GetTextChannel(channelID);
if (channel == null)
{
await this.RespondFailureAsync($"Channel with specified ID not found. {ResponseEmoji.Failure}").ConfigureAwait(false);
return;
}

try
{
BlogChannelProperties props = new BlogChannelProperties()
{
NSFW = nsfw
};

await this._manager.EditBlogChannel(channel, props, base.CancellationToken).ConfigureAwait(false);
await base.ModifyOriginalResponseAsync(msg =>
{
msg.Content = null;
msg.Embed = new EmbedBuilder()
.WithAuthor(callerUser)
.WithTitle($"Blog Channel updated!")
.WithDescription($"{ResponseEmoji.ParrotParty}{ResponseEmoji.ParrotParty}{ResponseEmoji.ParrotParty}{ResponseEmoji.ParrotParty}{ResponseEmoji.ParrotParty}")
.WithColor(callerUser.GetUserColour())
.AddField("Channel", MentionUtils.MentionChannel(channel.Id), inline: true)
.WithTimestamp(DateTime.UtcNow)
.WithFooter("DevSub Blogs yo!")
.Build();
msg.Components = new ComponentBuilder()
.WithButton("Go now!", null,
ButtonStyle.Link,
Emote.Parse(ResponseEmoji.EyesBlurry),
channel.GetURL())
.Build();
}, base.GetRequestOptions());
}
catch (HttpException ex) when (ex.IsMissingPermissions() && ex.LogAsError(this._log, "Exception when updating blog channel"))
{
await base.ModifyOriginalResponseAsync(msg => msg.Content = $"Oops! {ResponseEmoji.Failure}\nI lack permissions to update the channel! {ResponseEmoji.FeelsBeanMan}",
base.GetRequestOptions()).ConfigureAwait(false);
return;
}
}

private Task RespondFailureAsync(string text)
=> base.ModifyOriginalResponseAsync(msg =>
{
msg.Content = text;
msg.AllowedMentions = AllowedMentions.None;
}, base.GetRequestOptions());
}
}
Loading

0 comments on commit 5775716

Please sign in to comment.