mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-24 17:34:13 +00:00
Merge pull request #48 from NoiTheCat/dev/export-command
Split birthday exporting to its own command
This commit is contained in:
commit
4d1d0b4024
6 changed files with 169 additions and 164 deletions
|
@ -12,7 +12,6 @@ public class BirthdayModule : BotModuleBase {
|
||||||
public const string HelpCmdRemove = "Removes your birthday information from this bot.";
|
public const string HelpCmdRemove = "Removes your birthday information from this bot.";
|
||||||
public const string HelpCmdGet = "Gets a user's birthday.";
|
public const string HelpCmdGet = "Gets a user's birthday.";
|
||||||
public const string HelpCmdNearest = "Get a list of users who recently had or will have a birthday.";
|
public const string HelpCmdNearest = "Get a list of users who recently had or will have a birthday.";
|
||||||
public const string HelpCmdExport = "Generates a text file with all known and available birthdays.";
|
|
||||||
public const string ErrNotSetFk = $":x: The bot has not yet been set up. Please configure a birthday role."; // foreign key violation
|
public const string ErrNotSetFk = $":x: The bot has not yet been set up. Please configure a birthday role."; // foreign key violation
|
||||||
|
|
||||||
// Note that these methods have largely been copied to BirthdayOverrideModule. Changes here should be reflected there as needed.
|
// Note that these methods have largely been copied to BirthdayOverrideModule. Changes here should be reflected there as needed.
|
||||||
|
@ -190,152 +189,4 @@ public class BirthdayModule : BotModuleBase {
|
||||||
else
|
else
|
||||||
await doOutput(output.ToString()).ConfigureAwait(false);
|
await doOutput(output.ToString()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
|
|
||||||
[SlashCommand("export", HelpPfxModOnly + HelpCmdExport)]
|
|
||||||
public async Task CmdExport([Summary(description: "Specify whether to export the list in CSV format.")] bool asCsv = false) {
|
|
||||||
if (!await HasMemberCacheAsync(Context.Guild)) {
|
|
||||||
await RespondAsync(MemberCacheEmptyError, ephemeral: true).ConfigureAwait(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bdlist = GetSortedUserList(Context.Guild);
|
|
||||||
|
|
||||||
var filename = "birthdaybot-" + Context.Guild.Id;
|
|
||||||
Stream fileoutput;
|
|
||||||
if (asCsv) {
|
|
||||||
fileoutput = ListExportCsv(Context.Guild, bdlist);
|
|
||||||
filename += ".csv";
|
|
||||||
} else {
|
|
||||||
fileoutput = ListExportNormal(Context.Guild, bdlist);
|
|
||||||
filename += ".txt.";
|
|
||||||
}
|
|
||||||
await RespondWithFileAsync(fileoutput, filename, text: $"Exported {bdlist.Count} birthdays to file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Listing helper methods
|
|
||||||
/// <summary>
|
|
||||||
/// Fetches all guild birthdays and places them into an easily usable structure.
|
|
||||||
/// Users currently not in the guild are not included in the result.
|
|
||||||
/// </summary>
|
|
||||||
private static List<ListItem> GetSortedUserList(SocketGuild guild) {
|
|
||||||
using var db = new BotDatabaseContext();
|
|
||||||
var query = from row in db.UserEntries
|
|
||||||
where row.GuildId == guild.Id
|
|
||||||
orderby row.BirthMonth, row.BirthDay
|
|
||||||
select new {
|
|
||||||
row.UserId,
|
|
||||||
Month = row.BirthMonth,
|
|
||||||
Day = row.BirthDay,
|
|
||||||
Zone = row.TimeZone
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = new List<ListItem>();
|
|
||||||
foreach (var row in query) {
|
|
||||||
var guildUser = guild.GetUser(row.UserId);
|
|
||||||
if (guildUser == null) continue; // Skip user not in guild
|
|
||||||
|
|
||||||
result.Add(new ListItem() {
|
|
||||||
BirthMonth = row.Month,
|
|
||||||
BirthDay = row.Day,
|
|
||||||
DateIndex = DateIndex(row.Month, row.Day),
|
|
||||||
UserId = guildUser.Id,
|
|
||||||
DisplayName = Common.FormatName(guildUser, false),
|
|
||||||
TimeZone = row.Zone
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream ListExportNormal(SocketGuild guild, IEnumerable<ListItem> list) {
|
|
||||||
// Output: "● Mon-dd: (user ID) Username [ - Nickname: (nickname)]"
|
|
||||||
var result = new MemoryStream();
|
|
||||||
var writer = new StreamWriter(result, Encoding.UTF8);
|
|
||||||
|
|
||||||
writer.WriteLine("Birthdays in " + guild.Name);
|
|
||||||
writer.WriteLine();
|
|
||||||
foreach (var item in list) {
|
|
||||||
var user = guild.GetUser(item.UserId);
|
|
||||||
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
|
||||||
writer.Write($"● {Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}: ");
|
|
||||||
writer.Write(item.UserId);
|
|
||||||
writer.Write(" " + user.Username + "#" + user.Discriminator);
|
|
||||||
if (user.Nickname != null) writer.Write(" - Nickname: " + user.Nickname);
|
|
||||||
if (item.TimeZone != null) writer.Write(" | Time zone: " + item.TimeZone);
|
|
||||||
writer.WriteLine();
|
|
||||||
}
|
|
||||||
writer.Flush();
|
|
||||||
result.Position = 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream ListExportCsv(SocketGuild guild, IEnumerable<ListItem> list) {
|
|
||||||
// Output: User ID, Username, Nickname, Month-Day, Month, Day
|
|
||||||
var result = new MemoryStream();
|
|
||||||
var writer = new StreamWriter(result, Encoding.UTF8);
|
|
||||||
|
|
||||||
// Conforming to RFC 4180; with header
|
|
||||||
writer.Write("UserId,Username,Nickname,MonthDayDisp,Month,Day,TimeZone");
|
|
||||||
writer.Write("\r\n"); // crlf line break is specified by the standard
|
|
||||||
foreach (var item in list) {
|
|
||||||
var user = guild.GetUser(item.UserId);
|
|
||||||
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
|
||||||
writer.Write(item.UserId);
|
|
||||||
writer.Write(',');
|
|
||||||
writer.Write(CsvEscape(user.Username + "#" + user.Discriminator));
|
|
||||||
writer.Write(',');
|
|
||||||
if (user.Nickname != null) writer.Write(user.Nickname);
|
|
||||||
writer.Write(',');
|
|
||||||
writer.Write($"{Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}");
|
|
||||||
writer.Write(',');
|
|
||||||
writer.Write(item.BirthMonth);
|
|
||||||
writer.Write(',');
|
|
||||||
writer.Write(item.BirthDay);
|
|
||||||
writer.Write(',');
|
|
||||||
writer.Write(item.TimeZone);
|
|
||||||
writer.Write("\r\n");
|
|
||||||
}
|
|
||||||
writer.Flush();
|
|
||||||
result.Position = 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CsvEscape(string input) {
|
|
||||||
var result = new StringBuilder();
|
|
||||||
result.Append('"');
|
|
||||||
foreach (var ch in input) {
|
|
||||||
if (ch == '"') result.Append('"');
|
|
||||||
result.Append(ch);
|
|
||||||
}
|
|
||||||
result.Append('"');
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int DateIndex(int month, int day) {
|
|
||||||
var dateindex = 0;
|
|
||||||
// Add month offsets
|
|
||||||
if (month > 1) dateindex += 31; // Offset January
|
|
||||||
if (month > 2) dateindex += 29; // Offset February (incl. leap day)
|
|
||||||
if (month > 3) dateindex += 31; // etc
|
|
||||||
if (month > 4) dateindex += 30;
|
|
||||||
if (month > 5) dateindex += 31;
|
|
||||||
if (month > 6) dateindex += 30;
|
|
||||||
if (month > 7) dateindex += 31;
|
|
||||||
if (month > 8) dateindex += 31;
|
|
||||||
if (month > 9) dateindex += 30;
|
|
||||||
if (month > 10) dateindex += 31;
|
|
||||||
if (month > 11) dateindex += 30;
|
|
||||||
dateindex += day;
|
|
||||||
return dateindex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ListItem {
|
|
||||||
public int DateIndex;
|
|
||||||
public int BirthMonth;
|
|
||||||
public int BirthDay;
|
|
||||||
public ulong UserId;
|
|
||||||
public string DisplayName;
|
|
||||||
public string? TimeZone;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ public class BirthdayOverrideModule : BotModuleBase {
|
||||||
// Note that these methods have largely been copied from BirthdayModule. Changes there should be reflected here as needed.
|
// Note that these methods have largely been copied from BirthdayModule. Changes there should be reflected here as needed.
|
||||||
// TODO possible to use a common base class for shared functionality instead?
|
// TODO possible to use a common base class for shared functionality instead?
|
||||||
|
|
||||||
[SlashCommand("set-birthday", HelpPfxModOnly + "Set a user's birthday on their behalf.")]
|
[SlashCommand("set-birthday", "Set a user's birthday on their behalf.")]
|
||||||
public async Task OvSetBirthday([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
|
public async Task OvSetBirthday([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
|
||||||
[Summary(description: HelpOptDate)]string date) {
|
[Summary(description: HelpOptDate)]string date) {
|
||||||
int inmonth, inday;
|
int inmonth, inday;
|
||||||
|
@ -42,7 +42,7 @@ public class BirthdayOverrideModule : BotModuleBase {
|
||||||
$"**{FormatDate(inmonth, inday)}**.").ConfigureAwait(false);
|
$"**{FormatDate(inmonth, inday)}**.").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("set-timezone", HelpPfxModOnly + "Set a user's time zone on their behalf.")]
|
[SlashCommand("set-timezone", "Set a user's time zone on their behalf.")]
|
||||||
public async Task OvSetTimezone([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
|
public async Task OvSetTimezone([Summary(description: HelpOptOvTarget)]SocketGuildUser target,
|
||||||
[Summary(description: HelpOptZone)]string zone) {
|
[Summary(description: HelpOptZone)]string zone) {
|
||||||
using var db = new BotDatabaseContext();
|
using var db = new BotDatabaseContext();
|
||||||
|
@ -67,7 +67,7 @@ public class BirthdayOverrideModule : BotModuleBase {
|
||||||
$"**{newzone}**.").ConfigureAwait(false);
|
$"**{newzone}**.").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("remove-birthday", HelpPfxModOnly + "Remove a user's birthday information on their behalf.")]
|
[SlashCommand("remove-birthday", "Remove a user's birthday information on their behalf.")]
|
||||||
public async Task OvRemove([Summary(description: HelpOptOvTarget)]SocketGuildUser target) {
|
public async Task OvRemove([Summary(description: HelpOptOvTarget)]SocketGuildUser target) {
|
||||||
using var db = new BotDatabaseContext();
|
using var db = new BotDatabaseContext();
|
||||||
var user = target.GetUserEntryOrNew(db);
|
var user = target.GetUserEntryOrNew(db);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Discord.Interactions;
|
using BirthdayBot.Data;
|
||||||
|
using Discord.Interactions;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
@ -10,9 +11,6 @@ namespace BirthdayBot.ApplicationCommands;
|
||||||
/// Base class for our interaction module classes. Contains common data for use in implementing classes.
|
/// Base class for our interaction module classes. Contains common data for use in implementing classes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BotModuleBase : InteractionModuleBase<SocketInteractionContext> {
|
public abstract class BotModuleBase : InteractionModuleBase<SocketInteractionContext> {
|
||||||
protected const string HelpPfxModOnly = "Bot moderators only: ";
|
|
||||||
protected const string ErrGuildOnly = ":x: This command can only be run within a server.";
|
|
||||||
protected const string ErrNotAllowed = ":x: Only server moderators may use this command.";
|
|
||||||
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
protected const string MemberCacheEmptyError = ":warning: Please try the command again.";
|
||||||
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
|
public const string AccessDeniedError = ":warning: You are not allowed to run this command.";
|
||||||
|
|
||||||
|
@ -133,4 +131,66 @@ public abstract class BotModuleBase : InteractionModuleBase<SocketInteractionCon
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected static string FormatDate(int month, int day) => $"{day:00}-{Common.MonthNames[month]}";
|
protected static string FormatDate(int month, int day) => $"{day:00}-{Common.MonthNames[month]}";
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Listing helper methods
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches all guild birthdays and places them into an easily usable structure.
|
||||||
|
/// Users currently not in the guild are not included in the result.
|
||||||
|
/// </summary>
|
||||||
|
protected static List<ListItem> GetSortedUserList(SocketGuild guild) {
|
||||||
|
using var db = new BotDatabaseContext();
|
||||||
|
var query = from row in db.UserEntries
|
||||||
|
where row.GuildId == guild.Id
|
||||||
|
orderby row.BirthMonth, row.BirthDay
|
||||||
|
select new {
|
||||||
|
row.UserId,
|
||||||
|
Month = row.BirthMonth,
|
||||||
|
Day = row.BirthDay,
|
||||||
|
Zone = row.TimeZone
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = new List<ListItem>();
|
||||||
|
foreach (var row in query) {
|
||||||
|
var guildUser = guild.GetUser(row.UserId);
|
||||||
|
if (guildUser == null) continue; // Skip user not in guild
|
||||||
|
|
||||||
|
result.Add(new ListItem() {
|
||||||
|
BirthMonth = row.Month,
|
||||||
|
BirthDay = row.Day,
|
||||||
|
DateIndex = DateIndex(row.Month, row.Day),
|
||||||
|
UserId = guildUser.Id,
|
||||||
|
DisplayName = Common.FormatName(guildUser, false),
|
||||||
|
TimeZone = row.Zone
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int DateIndex(int month, int day) {
|
||||||
|
var dateindex = 0;
|
||||||
|
// Add month offsets
|
||||||
|
if (month > 1) dateindex += 31; // Offset January
|
||||||
|
if (month > 2) dateindex += 29; // Offset February (incl. leap day)
|
||||||
|
if (month > 3) dateindex += 31; // etc
|
||||||
|
if (month > 4) dateindex += 30;
|
||||||
|
if (month > 5) dateindex += 31;
|
||||||
|
if (month > 6) dateindex += 30;
|
||||||
|
if (month > 7) dateindex += 31;
|
||||||
|
if (month > 8) dateindex += 31;
|
||||||
|
if (month > 9) dateindex += 30;
|
||||||
|
if (month > 10) dateindex += 31;
|
||||||
|
if (month > 11) dateindex += 30;
|
||||||
|
dateindex += day;
|
||||||
|
return dateindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected struct ListItem {
|
||||||
|
public int DateIndex;
|
||||||
|
public int BirthMonth;
|
||||||
|
public int BirthDay;
|
||||||
|
public ulong UserId;
|
||||||
|
public string DisplayName;
|
||||||
|
public string? TimeZone;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ public class ConfigModule : BotModuleBase {
|
||||||
const string HelpOptChannel = "The corresponding channel to use.";
|
const string HelpOptChannel = "The corresponding channel to use.";
|
||||||
const string HelpOptRole = "The corresponding role to use.";
|
const string HelpOptRole = "The corresponding role to use.";
|
||||||
|
|
||||||
[Group("announce", HelpPfxModOnly + HelpCmdAnnounce)]
|
[Group("announce", HelpCmdAnnounce)]
|
||||||
public class SubCmdsConfigAnnounce : BotModuleBase {
|
public class SubCmdsConfigAnnounce : BotModuleBase {
|
||||||
private const string HelpSubCmdChannel = "Set which channel will receive announcement messages.";
|
private const string HelpSubCmdChannel = "Set which channel will receive announcement messages.";
|
||||||
private const string HelpSubCmdMessage = "Modify the announcement message.";
|
private const string HelpSubCmdMessage = "Modify the announcement message.";
|
||||||
|
@ -54,14 +54,14 @@ public class ConfigModule : BotModuleBase {
|
||||||
.Build()).ConfigureAwait(false);
|
.Build()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("set-channel", HelpPfxModOnly + HelpSubCmdChannel + HelpPofxBlankUnset)]
|
[SlashCommand("set-channel", HelpSubCmdChannel + HelpPofxBlankUnset)]
|
||||||
public async Task CmdSetChannel([Summary(description: HelpOptChannel)] SocketTextChannel? channel = null) {
|
public async Task CmdSetChannel([Summary(description: HelpOptChannel)] SocketTextChannel? channel = null) {
|
||||||
await DoDatabaseUpdate(Context, s => s.AnnouncementChannel = channel?.Id);
|
await DoDatabaseUpdate(Context, s => s.AnnouncementChannel = channel?.Id);
|
||||||
await RespondAsync(":white_check_mark: The announcement channel has been " +
|
await RespondAsync(":white_check_mark: The announcement channel has been " +
|
||||||
(channel == null ? "unset." : $"set to **{channel.Name}**."));
|
(channel == null ? "unset." : $"set to **{channel.Name}**."));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("set-message", HelpPfxModOnly + HelpSubCmdMessage)]
|
[SlashCommand("set-message", HelpSubCmdMessage)]
|
||||||
public async Task CmdSetMessage() {
|
public async Task CmdSetMessage() {
|
||||||
using var db = new BotDatabaseContext();
|
using var db = new BotDatabaseContext();
|
||||||
var settings = Context.Guild.GetConfigOrNew(db);
|
var settings = Context.Guild.GetConfigOrNew(db);
|
||||||
|
@ -111,14 +111,14 @@ public class ConfigModule : BotModuleBase {
|
||||||
await modal.RespondAsync(":white_check_mark: Announcement messages have been updated.");
|
await modal.RespondAsync(":white_check_mark: Announcement messages have been updated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("set-ping", HelpPfxModOnly + HelpSubCmdPing)]
|
[SlashCommand("set-ping", HelpSubCmdPing)]
|
||||||
public async Task CmdSetPing([Summary(description: "Set True to ping users, False to display them normally.")]bool option) {
|
public async Task CmdSetPing([Summary(description: "Set True to ping users, False to display them normally.")]bool option) {
|
||||||
await DoDatabaseUpdate(Context, s => s.AnnouncePing = option);
|
await DoDatabaseUpdate(Context, s => s.AnnouncePing = option);
|
||||||
await RespondAsync($":white_check_mark: Announcement pings are now **{(option ? "on" : "off")}**.").ConfigureAwait(false);
|
await RespondAsync($":white_check_mark: Announcement pings are now **{(option ? "on" : "off")}**.").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("birthday-role", HelpPfxModOnly + HelpCmdBirthdayRole)]
|
[SlashCommand("birthday-role", HelpCmdBirthdayRole)]
|
||||||
public async Task CmdSetBRole([Summary(description: HelpOptRole)] SocketRole role) {
|
public async Task CmdSetBRole([Summary(description: HelpOptRole)] SocketRole role) {
|
||||||
if (role.IsEveryone || role.IsManaged) {
|
if (role.IsEveryone || role.IsManaged) {
|
||||||
await RespondAsync(":x: This role cannot be used for this setting.", ephemeral: true);
|
await RespondAsync(":x: This role cannot be used for this setting.", ephemeral: true);
|
||||||
|
@ -128,7 +128,7 @@ public class ConfigModule : BotModuleBase {
|
||||||
await RespondAsync($":white_check_mark: The birthday role has been set to **{role.Name}**.").ConfigureAwait(false);
|
await RespondAsync($":white_check_mark: The birthday role has been set to **{role.Name}**.").ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("check", HelpPfxModOnly + HelpCmdCheck)]
|
[SlashCommand("check", HelpCmdCheck)]
|
||||||
public async Task CmdCheck() {
|
public async Task CmdCheck() {
|
||||||
static string DoTestFor(string label, Func<bool> test)
|
static string DoTestFor(string label, Func<bool> test)
|
||||||
=> $"{label}: { (test() ? ":white_check_mark: Yes" : ":x: No") }";
|
=> $"{label}: { (test() ? ":white_check_mark: Yes" : ":x: No") }";
|
||||||
|
@ -191,7 +191,7 @@ public class ConfigModule : BotModuleBase {
|
||||||
}.Build()).ConfigureAwait(false);
|
}.Build()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SlashCommand("set-timezone", HelpPfxModOnly + "Configure the time zone to use by default in the server." + HelpPofxBlankUnset)]
|
[SlashCommand("set-timezone", "Configure the time zone to use by default in the server." + HelpPofxBlankUnset)]
|
||||||
public async Task CmdSetTimezone([Summary(description: HelpOptZone)] string? zone = null) {
|
public async Task CmdSetTimezone([Summary(description: HelpOptZone)] string? zone = null) {
|
||||||
const string Response = ":white_check_mark: The server's time zone has been ";
|
const string Response = ":white_check_mark: The server's time zone has been ";
|
||||||
|
|
||||||
|
|
94
ApplicationCommands/ExportModule.cs
Normal file
94
ApplicationCommands/ExportModule.cs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
using Discord.Interactions;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BirthdayBot.ApplicationCommands;
|
||||||
|
public class ExportModule : BotModuleBase {
|
||||||
|
public const string HelpCmdExport = "Generates a text file with all known and available birthdays.";
|
||||||
|
|
||||||
|
[SlashCommand("export-birthdays", HelpCmdExport)]
|
||||||
|
[DefaultMemberPermissions(GuildPermission.ManageGuild)]
|
||||||
|
[EnabledInDm(false)]
|
||||||
|
public async Task CmdExport([Summary(description: "Specify whether to export the list in CSV format.")] bool asCsv = false) {
|
||||||
|
if (!await HasMemberCacheAsync(Context.Guild)) {
|
||||||
|
await RespondAsync(MemberCacheEmptyError, ephemeral: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bdlist = GetSortedUserList(Context.Guild);
|
||||||
|
|
||||||
|
var filename = "birthdaybot-" + Context.Guild.Id;
|
||||||
|
Stream fileoutput;
|
||||||
|
if (asCsv) {
|
||||||
|
fileoutput = ListExportCsv(Context.Guild, bdlist);
|
||||||
|
filename += ".csv";
|
||||||
|
} else {
|
||||||
|
fileoutput = ListExportNormal(Context.Guild, bdlist);
|
||||||
|
filename += ".txt.";
|
||||||
|
}
|
||||||
|
await RespondWithFileAsync(fileoutput, filename, text: $"Exported {bdlist.Count} birthdays to file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream ListExportNormal(SocketGuild guild, IEnumerable<ListItem> list) {
|
||||||
|
// Output: "● Mon-dd: (user ID) Username [ - Nickname: (nickname)]"
|
||||||
|
var result = new MemoryStream();
|
||||||
|
var writer = new StreamWriter(result, Encoding.UTF8);
|
||||||
|
|
||||||
|
writer.WriteLine("Birthdays in " + guild.Name);
|
||||||
|
writer.WriteLine();
|
||||||
|
foreach (var item in list) {
|
||||||
|
var user = guild.GetUser(item.UserId);
|
||||||
|
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
||||||
|
writer.Write($"● {Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}: ");
|
||||||
|
writer.Write(item.UserId);
|
||||||
|
writer.Write(" " + user.Username + "#" + user.Discriminator);
|
||||||
|
if (user.Nickname != null) writer.Write(" - Nickname: " + user.Nickname);
|
||||||
|
if (item.TimeZone != null) writer.Write(" | Time zone: " + item.TimeZone);
|
||||||
|
writer.WriteLine();
|
||||||
|
}
|
||||||
|
writer.Flush();
|
||||||
|
result.Position = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream ListExportCsv(SocketGuild guild, IEnumerable<ListItem> list) {
|
||||||
|
// Output: User ID, Username, Nickname, Month-Day, Month, Day
|
||||||
|
var result = new MemoryStream();
|
||||||
|
var writer = new StreamWriter(result, Encoding.UTF8);
|
||||||
|
|
||||||
|
static string csvEscape(string input) {
|
||||||
|
var result = new StringBuilder();
|
||||||
|
result.Append('"');
|
||||||
|
foreach (var ch in input) {
|
||||||
|
if (ch == '"') result.Append('"');
|
||||||
|
result.Append(ch);
|
||||||
|
}
|
||||||
|
result.Append('"');
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conforming to RFC 4180; with header
|
||||||
|
writer.Write("UserId,Username,Nickname,MonthDayDisp,Month,Day,TimeZone");
|
||||||
|
writer.Write("\r\n"); // crlf line break is specified by the standard
|
||||||
|
foreach (var item in list) {
|
||||||
|
var user = guild.GetUser(item.UserId);
|
||||||
|
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
||||||
|
writer.Write(item.UserId);
|
||||||
|
writer.Write(',');
|
||||||
|
writer.Write(csvEscape(user.Username + "#" + user.Discriminator));
|
||||||
|
writer.Write(',');
|
||||||
|
if (user.Nickname != null) writer.Write(user.Nickname);
|
||||||
|
writer.Write(',');
|
||||||
|
writer.Write($"{Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}");
|
||||||
|
writer.Write(',');
|
||||||
|
writer.Write(item.BirthMonth);
|
||||||
|
writer.Write(',');
|
||||||
|
writer.Write(item.BirthDay);
|
||||||
|
writer.Write(',');
|
||||||
|
writer.Write(item.TimeZone);
|
||||||
|
writer.Write("\r\n");
|
||||||
|
}
|
||||||
|
writer.Flush();
|
||||||
|
result.Position = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,12 @@ public class HelpModule : BotModuleBase {
|
||||||
$"` ⤷set timezone` - {BirthdayModule.HelpCmdSetZone}\n" +
|
$"` ⤷set timezone` - {BirthdayModule.HelpCmdSetZone}\n" +
|
||||||
$"` ⤷remove` - {BirthdayModule.HelpCmdRemove}";
|
$"` ⤷remove` - {BirthdayModule.HelpCmdRemove}";
|
||||||
private const string ModCommandsField =
|
private const string ModCommandsField =
|
||||||
$"`/birthday export` - {BirthdayModule.HelpCmdExport}\n" +
|
|
||||||
$"`/config` - {ConfigModule.HelpCmdConfig}\n" +
|
$"`/config` - {ConfigModule.HelpCmdConfig}\n" +
|
||||||
$"` ⤷check` - {ConfigModule.HelpCmdCheck}\n" +
|
$"` ⤷check` - {ConfigModule.HelpCmdCheck}\n" +
|
||||||
$"` ⤷announce` - {ConfigModule.HelpCmdAnnounce}\n" +
|
$"` ⤷announce` - {ConfigModule.HelpCmdAnnounce}\n" +
|
||||||
$"` ⤷` See also: `/config announce help`.\n" +
|
$"` ⤷` See also: `/config announce help`.\n" +
|
||||||
$"` ⤷birthday-role` - {ConfigModule.HelpCmdBirthdayRole}\n" +
|
$"` ⤷birthday-role` - {ConfigModule.HelpCmdBirthdayRole}\n" +
|
||||||
|
$"`/export-birthdays` - {ExportModule.HelpCmdExport}\n" +
|
||||||
$"`/override` - {BirthdayOverrideModule.HelpCmdOverride}\n" +
|
$"`/override` - {BirthdayOverrideModule.HelpCmdOverride}\n" +
|
||||||
$"` ⤷set-birthday`, `⤷set-timezone`, `⤷remove`\n" +
|
$"` ⤷set-birthday`, `⤷set-timezone`, `⤷remove`\n" +
|
||||||
"**Caution:** Skipping optional parameters __removes__ their configuration.";
|
"**Caution:** Skipping optional parameters __removes__ their configuration.";
|
||||||
|
|
Loading…
Reference in a new issue