Skip to content

Commit

Permalink
Merge pull request #343 from fit-ctu-discord/release/2022.3
Browse files Browse the repository at this point in the history
🔖 Upgrade version to 2022.3
  • Loading branch information
ostorc authored May 6, 2022
2 parents 8560917 + c341e07 commit 7cfbe18
Show file tree
Hide file tree
Showing 27 changed files with 382 additions and 203 deletions.
4 changes: 3 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ updates:
- package-ecosystem: "nuget" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
interval: "weekly"
commit-message:
prefix: "⬆️"
target-branch: "develop"
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>2022.2.2</Version>
<Version>2022.3</Version>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
Expand Down
16 changes: 8 additions & 8 deletions Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<ItemGroup>
<PackageReference Update="Chronic.Core" Version="0.4.0" />
<PackageReference Update="Cronos" Version="0.7.1" />
<PackageReference Update="DSharpPlus" Version="4.2.0-nightly-01105" />
<PackageReference Update="DSharpPlus.CommandsNext" Version="4.2.0-nightly-01105" />
<PackageReference Update="DSharpPlus.Interactivity" Version="4.2.0-nightly-01105" />
<PackageReference Update="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Update="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3" />
<PackageReference Update="DSharpPlus" Version="4.2.0" />
<PackageReference Update="DSharpPlus.CommandsNext" Version="4.2.0" />
<PackageReference Update="DSharpPlus.Interactivity" Version="4.2.0" />
<PackageReference Update="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Update="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4" />
<PackageReference Update="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Update="Microsoft.Extensions.Hosting" Version="6.0.1" />
Expand All @@ -16,8 +16,8 @@
<PackageReference Update="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Update="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
<PackageReference Update="Swashbuckle.AspNetCore" Version="6.3.0" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
<PackageReference Update="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageReference Update="System.Collections.Immutable" Version="6.0.0" />
<PackageReference Update="coverlet.collector" Version="3.1.2" />
<PackageReference Update="xunit" Version="2.4.1" />
Expand Down
2 changes: 1 addition & 1 deletion src/HonzaBotner.Discord.Services/Commands/FunCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ params string[] choices
await ctx.TriggerTypingAsync();

SecureRandom random = new();
string selected = choices[random.Next(choices.Length)].RemoveDiscordMentions(ctx.Guild, _logger);
string selected = choices[random.Next(choices.Length)].RemoveDiscordMentions(ctx.Guild);

try
{
Expand Down
6 changes: 3 additions & 3 deletions src/HonzaBotner.Discord.Services/Commands/PinCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ IReadOnlyDictionary<ulong, int> roleToScore
{
int score = 0;
IReadOnlyList<DiscordUser> reactions =
await message.GetReactionsAsync(permanentEmoji, _pinOptions.Treshold);
await message.GetReactionsAsync(permanentEmoji, _pinOptions.Threshold);

if (reactions.Count == _pinOptions.Treshold) continue;
if (reactions.Count == _pinOptions.Threshold) continue;

foreach (DiscordUser user in reactions)
{
Expand Down Expand Up @@ -177,7 +177,7 @@ IReadOnlyDictionary<ulong, int> roleToScore
}

// Do not delete anything.
if (score >= _pinOptions.Treshold)
if (score >= _pinOptions.Threshold)
{
continue;
}
Expand Down
43 changes: 24 additions & 19 deletions src/HonzaBotner.Discord.Services/Commands/PollCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ public class PollCommands : BaseCommandModule

private readonly CommonCommandOptions _options;
private readonly ILogger<PollCommands> _logger;
private readonly IGuildProvider _guildProvider;

public PollCommands(IOptions<CommonCommandOptions> options, ILogger<PollCommands> logger)
public PollCommands(IOptions<CommonCommandOptions> options, ILogger<PollCommands> logger,
IGuildProvider guildProvider)
{
_options = options.Value;
_logger = logger;
_guildProvider = guildProvider;
}

[GroupCommand]
Expand Down Expand Up @@ -83,27 +86,27 @@ public async Task AbcPollCommandAsync(

private async Task CreateDefaultPollAsync(CommandContext ctx, string question, List<string>? answers = null)
{
Poll poll = answers is null
? new YesNoPoll(ctx.Member.Mention, question)
: new AbcPoll(ctx.Member.Mention, question, answers);

try
{
Poll poll = answers is null
? new YesNoPoll(ctx.Member.Mention, question)
: new AbcPoll(ctx.Member.Mention, question, answers);

await poll.PostAsync(ctx.Client, ctx.Channel);
await ctx.Message.DeleteAsync();
}
catch (ArgumentException e)
catch (PollException e)
{
await ctx.RespondAsync(e.Message);
}
catch (Exception e)
{
await ctx.RespondAsync(PollErrorMessage);
_logger.LogWarning(e, "Failed to create new {PollType}", poll.PollType);
_logger.LogWarning(e, "Failed to create new Poll");
}
}

private async Task PollHelpAsync(CommandContext ctx)
private static async Task PollHelpAsync(CommandContext ctx)
{
DiscordEmbed embed = new DiscordEmbedBuilder()
.WithTitle("Polls")
Expand Down Expand Up @@ -146,21 +149,23 @@ public async Task AddPollOptionAsync(
return;
}

DiscordRole modRole = (await ctx.Client.GetGuildAsync(ctx.Guild.Id)).GetRole(_options.ModRoleId);
AbcPoll poll = new(originalMessage);

if (poll.AuthorMention != ctx.Member.Mention && !ctx.Member.Roles.Contains(modRole))
{
await ctx.RespondAsync("You are not authorized to edit this poll");
return;
}

try
{
await new AbcPoll(originalMessage).AddOptionsAsync(ctx.Client, options.ToList());
DiscordRole modRole = (await _guildProvider.GetCurrentGuildAsync()).GetRole(_options.ModRoleId);

// I am sorry, due to DSharpPlus' caching logic, this mess is necessary
AbcPoll poll = new (await (await ctx.Client.GetChannelAsync(ctx.Channel.Id)).GetMessageAsync(ctx.Message.ReferencedMessage.Id));

if (poll.AuthorMention != ctx.Member?.Mention && !(ctx.Member?.Roles.Contains(modRole) ?? false))
{
await ctx.RespondAsync("You are not authorized to edit this poll");
return;
}

await poll.AddOptionsAsync(ctx.Client, options);
await ctx.Message.CreateReactionAsync(DiscordEmoji.FromName(ctx.Client, ":+1:"));
}
catch (ArgumentException e)
catch (PollException e)
{
await ctx.RespondAsync(e.Message);
}
Expand Down
64 changes: 62 additions & 2 deletions src/HonzaBotner.Discord.Services/Commands/Polls/AbcPoll.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.Entities;
using HonzaBotner.Discord.Services.Extensions;

namespace HonzaBotner.Discord.Services.Commands.Polls;

public class AbcPoll : Poll
{
public override string PollType => "AbcPoll";

public override List<string> OptionsEmoji => new()
protected override List<string> OptionsEmoji => new()
{
":regional_indicator_a:",
":regional_indicator_b:",
Expand Down Expand Up @@ -35,4 +40,59 @@ public AbcPoll(string authorMention, string question, List<string> options)
public AbcPoll(DiscordMessage message) : base(message)
{
}

public async Task AddOptionsAsync(DiscordClient client, IEnumerable<string> newOptions)
{
const int reactionCap = 20; // Max amount of reactions present on a message, lower than Discord provided, in case some trolls block bot's reactions
if (ExistingPollMessage is null)
{
throw new InvalidOperationException("You can edit only poll constructed from sent message.");
}

NewChoices = newOptions.ToList();

List<string> emojisToAdd = OptionsEmoji;

// Look at existing message and allow only emojis which are not yet present on that message.
emojisToAdd.RemoveAll(emoji =>
ExistingPollMessage.Reactions.Select(rect => rect.Emoji).Contains(DiscordEmoji.FromName(client, emoji)));

emojisToAdd = emojisToAdd.GetRange(
0,
// Allow only so many reactions, that we don't cross 20 reactions on existing message
Math.Min(Math.Min(reactionCap - Math.Min(ExistingPollMessage.Reactions.Count, reactionCap),
// Take above reaction capacity, and lower it optionally to number of emojis which we are able to react with
emojisToAdd.Count),
// Take the above number and cap it at total new choices we want to add (can be lower or equal to real choices number)
NewChoices.Count));

// The new options count will be equal or lower than total options added, based on available emojis
NewChoices = NewChoices.GetRange(0, emojisToAdd.Count);

if (NewChoices.Count == 0)
{
throw new PollException($"Total number of reactions on a message can't be greater than {reactionCap}");
}
await ExistingPollMessage
.ModifyAsync(Modify(client, ExistingPollMessage.Channel.Guild, ExistingPollMessage.Embeds[0], emojisToAdd));

Task _ = Task.Run(async () => { await AddReactionsAsync(client, ExistingPollMessage, emojisToAdd); });
}

private DiscordEmbed Modify(DiscordClient client, DiscordGuild guild, DiscordEmbed original, IEnumerable<string> emojisToAdd)
{
DiscordEmbedBuilder builder = new (original);

NewChoices.Zip(emojisToAdd).ToList().ForEach(pair =>
{
(string? answer, string? emojiName) = pair;

builder.AddField(
DiscordEmoji.FromName(client, emojiName).ToString(),
answer.RemoveDiscordMentions(guild),
true);
});

return builder.WithFooter(PollType).Build();
}
}
54 changes: 18 additions & 36 deletions src/HonzaBotner.Discord.Services/Commands/Polls/Poll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,37 @@ namespace HonzaBotner.Discord.Services.Commands.Polls;

public abstract class Poll
{
public abstract List<string> OptionsEmoji { get; }
protected abstract List<string> OptionsEmoji { get; }
public abstract string PollType { get; }

public virtual List<string> ActiveEmojis
protected virtual List<string> UsedEmojis
{
get => OptionsEmoji.GetRange(0, _choices.Count);
get => OptionsEmoji.GetRange(0, NewChoices.Count);
}

protected List<string> NewChoices;

public readonly string AuthorMention;
private readonly List<string> _choices;
private readonly DiscordMessage? _existingPollMessage;
private readonly string _question;
protected readonly DiscordMessage? ExistingPollMessage;
protected readonly string Question;

protected Poll(string authorMention, string question, List<string>? options = null)
{
AuthorMention = authorMention;
_question = question;
_choices = options ?? new List<string>();
Question = question;
NewChoices = options ?? new List<string>();
}

protected Poll(DiscordMessage originalMessage)
{
_existingPollMessage = originalMessage;
DiscordEmbed originalPoll = _existingPollMessage.Embeds[0];
ExistingPollMessage = originalMessage;
DiscordEmbed originalPoll = ExistingPollMessage.Embeds[0];

// Extract original author Mention via discord's mention format <@!123456789>.
AuthorMention = originalPoll.Description.Substring(
originalPoll.Description.LastIndexOf("<", StringComparison.Ordinal)
);

_choices = originalPoll.Fields?
.Select(ef => ef.Value)
.ToList() ?? new List<string>();

_question = originalPoll.Title;
Question = originalPoll.Title;
}

public async Task PostAsync(DiscordClient client, DiscordChannel channel)
Expand All @@ -54,42 +50,28 @@ public async Task PostAsync(DiscordClient client, DiscordChannel channel)
Task _ = Task.Run(async () => { await AddReactionsAsync(client, pollMessage); });
}

public virtual async Task AddOptionsAsync(DiscordClient client, IEnumerable<string> newOptions)
{
if (_existingPollMessage == null)
{
throw new InvalidOperationException("You can edit only poll constructed from sent message.");
}

_choices.AddRange(newOptions);

await _existingPollMessage.ModifyAsync(Build(client, _existingPollMessage.Channel.Guild));

Task _ = Task.Run(async () => { await AddReactionsAsync(client, _existingPollMessage); });
}

protected async Task AddReactionsAsync(DiscordClient client, DiscordMessage message)
protected async Task AddReactionsAsync(DiscordClient client, DiscordMessage message, List<string>? reactions = null)
{
foreach (string reaction in ActiveEmojis)
foreach (string reaction in reactions ?? UsedEmojis)
{
await message.CreateReactionAsync(DiscordEmoji.FromName(client, reaction));
}
}

private DiscordEmbed Build(DiscordClient client, DiscordGuild guild)
{
if (_choices.Count > OptionsEmoji.Count)
if (NewChoices.Count > OptionsEmoji.Count)
{
throw new ArgumentException($"Too many options. Maximum options is {OptionsEmoji.Count}.");
throw new PollException($"Too many options. Maximum options is {OptionsEmoji.Count}.");
}

DiscordEmbedBuilder builder = new()
{
Title = _question.RemoveDiscordMentions(guild),
Title = Question.RemoveDiscordMentions(guild),
Description = "By: " + AuthorMention // Author needs to stay as the last argument
};

_choices.Zip(ActiveEmojis).ToList().ForEach(pair =>
NewChoices.Zip(UsedEmojis).ToList().ForEach(pair =>
{
(string? answer, string? emojiName) = pair;

Expand Down
17 changes: 17 additions & 0 deletions src/HonzaBotner.Discord.Services/Commands/Polls/PollException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace HonzaBotner.Discord.Services.Commands.Polls;

public class PollException : Exception
{
public PollException ()
{}

public PollException (string message)
: base(message)
{}

public PollException (string message, Exception innerException)
: base (message, innerException)
{}
}
16 changes: 7 additions & 9 deletions src/HonzaBotner.Discord.Services/Commands/Polls/YesNoPoll.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using DSharpPlus;
using System.Collections.Generic;
using DSharpPlus.Entities;

namespace HonzaBotner.Discord.Services.Commands.Polls;

public class YesNoPoll : Poll
{
public override string PollType => "YesNoPoll";
public override List<string> OptionsEmoji => new() { ":+1:", ":-1:" };
public override List<string> ActiveEmojis => OptionsEmoji;
protected override List<string> OptionsEmoji => new() { ":+1:", ":-1:" };

protected override List<string> UsedEmojis
{
get => OptionsEmoji;
}

public YesNoPoll(string authorMention, string question) : base(authorMention, question)
{
Expand All @@ -19,7 +20,4 @@ public YesNoPoll(string authorMention, string question) : base(authorMention, qu
public YesNoPoll(DiscordMessage message) : base(message)
{
}

public override Task AddOptionsAsync(DiscordClient client, IEnumerable<string> newOptions) =>
throw new ArgumentException($"Adding options is disabled for {PollType}");
}
Loading

0 comments on commit 7cfbe18

Please sign in to comment.