Skip to content

Commit 15fb03e

Browse files
committed
feat: notify members on status change
1 parent 3fe411b commit 15fb03e

8 files changed

Lines changed: 306 additions & 3 deletions

File tree

SuggestionBot/Commands/SuggestionCommand.SetStatus.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using DSharpPlus.Entities;
1+
using DSharpPlus.Entities;
22
using DSharpPlus.SlashCommands;
33
using Humanizer;
44
using SuggestionBot.AutocompleteProviders;
@@ -39,6 +39,12 @@ public async Task SetStatusAsync(InteractionContext context,
3939
embed.WithColor(DiscordColor.Orange);
4040
embed.WithTitle("Suggestion Status Changed");
4141
embed.WithDescription($"The suggestion with the ID {suggestion.Id} has been marked as {humanizedStatus}.");
42+
if (!string.IsNullOrWhiteSpace(remarks))
43+
{
44+
embed.AddField("Staff Remarks", remarks);
45+
}
46+
47+
await _mailmanService.SendSuggestionAsync(suggestion).ConfigureAwait(false);
4248
}
4349
else
4450
{

SuggestionBot/Commands/SuggestionCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ internal sealed partial class SuggestionCommand : ApplicationCommandModule
1616

1717
private readonly SuggestionService _suggestionService;
1818
private readonly UserBlockingService _userBlockingService;
19+
private readonly MailmanService _mailmanService;
1920

2021
/// <summary>
2122
/// Initializes a new instance of the <see cref="SuggestionCommand" /> class.
2223
/// </summary>
2324
/// <param name="suggestionService">The <see cref="SuggestionService" />.</param>
2425
/// <param name="userBlockingService">The <see cref="UserBlockingService" />.</param>
25-
public SuggestionCommand(SuggestionService suggestionService, UserBlockingService userBlockingService)
26+
/// <param name="mailmanService">The <see cref="MailmanService" />.</param>
27+
public SuggestionCommand(SuggestionService suggestionService,
28+
UserBlockingService userBlockingService,
29+
MailmanService mailmanService)
2630
{
2731
_suggestionService = suggestionService;
2832
_userBlockingService = userBlockingService;
33+
_mailmanService = mailmanService;
2934
}
3035

3136
private static DiscordEmbed CreateNotFoundEmbed(string query)

SuggestionBot/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
builder.Services.AddSingleton<CooldownService>();
3131
builder.Services.AddHostedSingleton<SuggestionService>();
3232
builder.Services.AddHostedSingleton<UserBlockingService>();
33+
builder.Services.AddSingleton<MailmanService>();
3334
builder.Services.AddHostedSingleton<BotService>();
3435

3536
IHost app = builder.Build();

SuggestionBot/Resources/PrivateMessages.Designer.cs

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<root>
4+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5+
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
6+
<xsd:element name="root" msdata:IsDataSet="true">
7+
8+
</xsd:element>
9+
</xsd:schema>
10+
<resheader name="resmimetype">
11+
<value>text/microsoft-resx</value>
12+
</resheader>
13+
<resheader name="version">
14+
<value>1.3</value>
15+
</resheader>
16+
<resheader name="reader">
17+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
18+
PublicKeyToken=b77a5c561934e089
19+
</value>
20+
</resheader>
21+
<resheader name="writer">
22+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
23+
PublicKeyToken=b77a5c561934e089
24+
</value>
25+
</resheader>
26+
27+
<data name="RejectedDescription" xml:space="preserve">
28+
<value>Hi {user.Mention}. Unfortunately, your suggestion in **{guild.Name}** has been rejected by staff at this time.
29+
Keep in mind that while a suggestion may be popular, there is no guarantee that it will be accepted. Nevertheless, we thank you for your contribution and feedback.
30+
31+
If you have any further questions, please reach us by sending a DM to ModMail.</value>
32+
</data>
33+
34+
<data name="AcceptedDescription" xml:space="preserve">
35+
<value>Hi {user.Mention}. Your suggestion in **{guild.Name}** has been accepted by staff members.
36+
Depending on the complexity of your suggestion, it may take time to implement it into the server. Please be patient during this process.
37+
38+
If you have any further questions, please reach us by sending a DM to ModMail.</value>
39+
</data>
40+
41+
<data name="ImplementedDescription" xml:space="preserve">
42+
<value>Hi {user.Mention}. Your suggestion in **{guild.Name}** has been implemented and is now live in the server!
43+
Thank you for your contribution and feedback, and we hope you enjoy the new changes.
44+
45+
If you have any further questions, please reach us by sending a DM to ModMail.</value>
46+
</data>
47+
</root>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using DSharpPlus;
2+
using DSharpPlus.Entities;
3+
using Humanizer;
4+
using SmartFormat;
5+
using SuggestionBot.Configuration;
6+
using SuggestionBot.Data;
7+
using SuggestionBot.Resources;
8+
using X10D.DSharpPlus;
9+
10+
namespace SuggestionBot.Services;
11+
12+
internal sealed class MailmanService
13+
{
14+
private readonly DiscordClient _discordClient;
15+
private readonly ConfigurationService _configurationService;
16+
private readonly SuggestionService _suggestionService;
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="MailmanService" /> class.
20+
/// </summary>
21+
/// <param name="discordClient">The <see cref="DiscordClient" />.</param>
22+
/// <param name="configurationService">The <see cref="ConfigurationService" />.</param>
23+
/// <param name="suggestionService">The <see cref="SuggestionService" />.</param>
24+
public MailmanService(DiscordClient discordClient,
25+
ConfigurationService configurationService,
26+
SuggestionService suggestionService)
27+
{
28+
_discordClient = discordClient;
29+
_configurationService = configurationService;
30+
_suggestionService = suggestionService;
31+
}
32+
33+
/// <summary>
34+
/// Sends a suggestion to the author via DM.
35+
/// </summary>
36+
/// <param name="suggestion">The <see cref="Suggestion" /> to send.</param>
37+
/// <exception cref="ArgumentNullException"><paramref name="suggestion" /> is <see langword="null" />.</exception>
38+
public async Task SendSuggestionAsync(Suggestion suggestion)
39+
{
40+
if (suggestion is null)
41+
{
42+
throw new ArgumentNullException(nameof(suggestion));
43+
}
44+
45+
if (!_discordClient.Guilds.TryGetValue(suggestion.GuildId, out DiscordGuild? guild))
46+
{
47+
return;
48+
}
49+
50+
DiscordUser author = _suggestionService.GetAuthor(suggestion);
51+
DiscordMember? member = await author.GetAsMemberOfAsync(guild).ConfigureAwait(false);
52+
if (member is null)
53+
{
54+
return;
55+
}
56+
57+
Uri suggestionLink = _suggestionService.GetSuggestionLink(suggestion);
58+
string iconUrl = guild.GetIconUrl(ImageFormat.Png);
59+
60+
var embed = new DiscordEmbedBuilder();
61+
embed.WithThumbnail(iconUrl);
62+
embed.WithFooter(guild.Name, iconUrl);
63+
64+
if (!TryBuildEmbed(suggestion, embed, author, guild))
65+
{
66+
return;
67+
}
68+
69+
suggestion.Content = suggestion.Content.Truncate(250, "...");
70+
embed.AddField("Your Suggestion", suggestion.Content);
71+
72+
if (!string.IsNullOrWhiteSpace(suggestion.Remarks))
73+
{
74+
embed.AddField("Staff Remarks", suggestion.Remarks);
75+
}
76+
77+
await member.SendMessageAsync(embed).ConfigureAwait(false);
78+
}
79+
80+
private bool TryBuildEmbed(Suggestion suggestion, DiscordEmbedBuilder embed, DiscordUser author, DiscordGuild guild)
81+
{
82+
if (!_configurationService.TryGetGuildConfiguration(guild, out GuildConfiguration? configuration))
83+
{
84+
configuration = new GuildConfiguration();
85+
}
86+
87+
switch (suggestion.Status)
88+
{
89+
case SuggestionStatus.Rejected:
90+
embed.WithColor(configuration.RejectedColor);
91+
embed.WithTitle("Suggestion Rejected");
92+
embed.WithDescription(PrivateMessages.RejectedDescription.FormatSmart(new { user = author, guild }));
93+
break;
94+
95+
case SuggestionStatus.Accepted:
96+
embed.WithColor(configuration.AcceptedColor);
97+
embed.WithTitle("Suggestion Accepted");
98+
embed.WithDescription(PrivateMessages.AcceptedDescription.FormatSmart(new { user = author, guild }));
99+
break;
100+
101+
case SuggestionStatus.Implemented:
102+
embed.WithColor(configuration.ImplementedColor);
103+
embed.WithTitle("Suggestion Implemented");
104+
embed.WithDescription(PrivateMessages.ImplementedDescription.FormatSmart(new { user = author, guild }));
105+
break;
106+
107+
default:
108+
return false;
109+
}
110+
111+
return true;
112+
}
113+
}

SuggestionBot/Services/SuggestionService.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public DiscordEmbed CreatePrivateEmbed(Suggestion suggestion)
9999
embed.AddField("Submitted", Formatter.Timestamp(suggestion.Timestamp), true);
100100
embed.AddField("View Suggestion", GetSuggestionLink(suggestion), true);
101101

102+
if (suggestion.ThreadId != 0)
103+
{
104+
embed.AddField("View Discussion", MentionUtility.MentionChannel(suggestion.ThreadId), true);
105+
}
106+
102107
if (suggestion.StaffMemberId.HasValue)
103108
{
104109
embed.AddField("Approver", MentionUtility.MentionUser(suggestion.StaffMemberId.Value), true);
@@ -486,8 +491,20 @@ public bool SetStatus(Suggestion suggestion,
486491
string humanizedStatus = status.Humanize(LetterCasing.AllCaps);
487492
string oldHumanizedStatus = suggestion.Status.Humanize(LetterCasing.AllCaps);
488493

494+
if (!_configurationService.TryGetGuildConfiguration(suggestion.GuildId, out GuildConfiguration? configuration))
495+
{
496+
configuration = new GuildConfiguration();
497+
}
498+
489499
var embed = new DiscordEmbedBuilder();
490-
embed.WithColor(DiscordColor.CornflowerBlue);
500+
embed.WithColor(status switch
501+
{
502+
SuggestionStatus.Suggested => configuration.SuggestedColor,
503+
SuggestionStatus.Rejected => configuration.RejectedColor,
504+
SuggestionStatus.Implemented => configuration.ImplementedColor,
505+
SuggestionStatus.Accepted => configuration.AcceptedColor,
506+
_ => DiscordColor.CornflowerBlue
507+
});
491508
embed.WithTitle("Suggestion Status Updated");
492509
embed.WithDescription($"The status of suggestion {suggestion.Id} has been updated to **{humanizedStatus}**.");
493510
embed.AddField("Old Status", oldHumanizedStatus, true);

SuggestionBot/SuggestionBot.csproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1"/>
4141
<PackageReference Include="NLog" Version="5.2.2"/>
4242
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.2"/>
43+
<PackageReference Include="SmartFormat" Version="3.2.1"/>
4344
<PackageReference Include="X10D" Version="3.2.2"/>
4445
<PackageReference Include="X10D.DSharpPlus" Version="3.2.2"/>
4546
<PackageReference Include="X10D.Hosting" Version="3.2.2"/>
@@ -54,4 +55,19 @@
5455
</Content>
5556
</ItemGroup>
5657

58+
<ItemGroup>
59+
<EmbeddedResource Update="Resources\PrivateMessages.resx">
60+
<Generator>ResXFileCodeGenerator</Generator>
61+
<LastGenOutput>PrivateMessages.Designer.cs</LastGenOutput>
62+
</EmbeddedResource>
63+
</ItemGroup>
64+
65+
<ItemGroup>
66+
<Compile Update="Resources\PrivateMessages.Designer.cs">
67+
<DesignTime>True</DesignTime>
68+
<AutoGen>True</AutoGen>
69+
<DependentUpon>PrivateMessages.resx</DependentUpon>
70+
</Compile>
71+
</ItemGroup>
72+
5773
</Project>

0 commit comments

Comments
 (0)