mirror of
https://github.com/NoiTheCat/BirthdayBot.git
synced 2024-11-22 05:54:36 +00:00
Noi
ddcde10e09
All existing VB code was 'translated' to C# as closely as possible, with minor changes and additional notes. Currently untested and likely broken. Further commits will go toward making overall improvements until this version replaces the currently existing code.
271 lines
11 KiB
C#
271 lines
11 KiB
C#
using BirthdayBot.Data;
|
|
using Discord.WebSocket;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace BirthdayBot.UserInterface
|
|
{
|
|
/// <summary>
|
|
/// Commands for listing upcoming and all birthdays.
|
|
/// </summary>
|
|
internal class ListingCommands : CommandsCommon
|
|
{
|
|
public ListingCommands(BirthdayBot inst, Configuration db) : base(inst, db) { }
|
|
|
|
public override IEnumerable<(string, CommandHandler)> Commands
|
|
=> new List<(string, CommandHandler)>()
|
|
{
|
|
("list", CmdList),
|
|
("upcoming", CmdUpcoming),
|
|
("recent", CmdUpcoming)
|
|
};
|
|
|
|
// Creates a file with all birthdays.
|
|
private async Task CmdList(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
|
|
{
|
|
// For now, we're restricting this command to moderators only. This may turn into an option later.
|
|
if (!Instance.GuildCache[reqChannel.Guild.Id].IsUserModerator(reqUser))
|
|
{
|
|
await reqChannel.SendMessageAsync(":x: Only bot moderators may use this command.");
|
|
return;
|
|
}
|
|
|
|
bool useCsv = false;
|
|
// Check for CSV option
|
|
if (param.Length == 2)
|
|
{
|
|
if (param[1].ToLower() == "csv") useCsv = true;
|
|
else
|
|
{
|
|
await reqChannel.SendMessageAsync(":x: That is not available as an export format.");
|
|
return;
|
|
}
|
|
}
|
|
else if (param.Length > 2)
|
|
{
|
|
await reqChannel.SendMessageAsync(GenericError);
|
|
return;
|
|
}
|
|
|
|
var bdlist = await LoadList(reqChannel.Guild, false);
|
|
|
|
var filepath = Path.GetTempPath() + "birthdaybot-" + reqChannel.Guild.Id;
|
|
string fileoutput;
|
|
if (useCsv)
|
|
{
|
|
fileoutput = ListExportCsv(reqChannel, bdlist);
|
|
filepath += ".csv";
|
|
}
|
|
else
|
|
{
|
|
fileoutput = ListExportNormal(reqChannel, bdlist);
|
|
filepath += ".txt.";
|
|
}
|
|
await File.WriteAllTextAsync(filepath, fileoutput, Encoding.UTF8);
|
|
|
|
try
|
|
{
|
|
await reqChannel.SendFileAsync(filepath, $"Exported {bdlist.Count} birthdays to file.");
|
|
}
|
|
catch (Discord.Net.HttpException)
|
|
{
|
|
reqChannel.SendMessageAsync(":x: Unable to send list due to a permissions issue. Check the 'Attach Files' permission.").Wait();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Program.Log("Listing", ex.ToString());
|
|
reqChannel.SendMessageAsync(":x: An internal error occurred. It has been reported to the bot owner.").Wait();
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(filepath);
|
|
}
|
|
}
|
|
|
|
// "Recent and upcoming birthdays"
|
|
// The 'recent' bit removes time zone ambiguity and spares us from extra time zone processing here
|
|
private async Task CmdUpcoming(string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser)
|
|
{
|
|
var now = DateTimeOffset.UtcNow;
|
|
var search = DateIndex(now.Month, now.Day) - 4; // begin search 4 days prior to current date UTC
|
|
if (search <= 0) search = 366 - Math.Abs(search);
|
|
|
|
var query = await LoadList(reqChannel.Guild, true);
|
|
|
|
var output = new StringBuilder();
|
|
var resultCount = 0;
|
|
output.AppendLine("Recent and upcoming birthdays:");
|
|
for (int count = 0; count <= 11; count++) // cover 11 days total (3 prior, current day, 7 upcoming)
|
|
{
|
|
var results = from item in query
|
|
where item.DateIndex == search
|
|
select item;
|
|
|
|
// push up search by 1 now, in case we back out early
|
|
search += 1;
|
|
if (search > 366) search = 1; // wrap to beginning of year
|
|
|
|
if (results.Count() == 0) continue; // back out early
|
|
resultCount += results.Count();
|
|
|
|
// Build sorted name list
|
|
var names = new List<string>();
|
|
foreach (var item in results)
|
|
{
|
|
names.Add(item.DisplayName);
|
|
}
|
|
names.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
var first = true;
|
|
output.AppendLine();
|
|
output.Append($"● `{Common.MonthNames[results.First().BirthMonth]}-{results.First().BirthDay.ToString("00")}`: ");
|
|
foreach (var item in names)
|
|
{
|
|
if (first) first = false;
|
|
else output.Append(", ");
|
|
output.Append(item);
|
|
}
|
|
}
|
|
|
|
if (resultCount == 0)
|
|
await reqChannel.SendMessageAsync("There are no recent or upcoming birthdays (within the last 3 days and/or next 7 days).");
|
|
else
|
|
await reqChannel.SendMessageAsync(output.ToString());
|
|
}
|
|
|
|
/// <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 async Task<List<ListItem>> LoadList(SocketGuild guild, bool escapeFormat)
|
|
{
|
|
var ping = Instance.GuildCache[guild.Id].AnnouncePing;
|
|
|
|
using (var db = await BotConfig.DatabaseSettings.OpenConnectionAsync())
|
|
{
|
|
using (var c = db.CreateCommand())
|
|
{
|
|
c.CommandText = "select user_id, birth_month, birth_day from " + GuildUserSettings.BackingTable
|
|
+ " where guild_id = @Gid order by birth_month, birth_day";
|
|
c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guild.Id;
|
|
c.Prepare();
|
|
using (var r = await c.ExecuteReaderAsync())
|
|
{
|
|
var result = new List<ListItem>();
|
|
while (await r.ReadAsync())
|
|
{
|
|
var id = (ulong)r.GetInt64(0);
|
|
var month = r.GetInt32(1);
|
|
var day = r.GetInt32(2);
|
|
|
|
var guildUser = guild.GetUser(id);
|
|
if (guildUser == null) continue; // Skip users not in guild
|
|
|
|
result.Add(new ListItem()
|
|
{
|
|
BirthMonth = month,
|
|
BirthDay = day,
|
|
DateIndex = DateIndex(month, day),
|
|
UserId = guildUser.Id,
|
|
DisplayName = Common.FormatName(guildUser, false)
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private string ListExportNormal(SocketGuildChannel channel, IEnumerable<ListItem> list)
|
|
{
|
|
// Output: "● Mon-dd: (user ID) Username [ - Nickname: (nickname)]"
|
|
var result = new StringBuilder();
|
|
result.AppendLine("Birthdays in " + channel.Guild.Name);
|
|
result.AppendLine();
|
|
foreach (var item in list)
|
|
{
|
|
var user = channel.Guild.GetUser(item.UserId);
|
|
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
|
result.Append($"● {Common.MonthNames[item.BirthMonth]}-{item.BirthDay:00}: ");
|
|
result.Append(item.UserId);
|
|
result.Append(" " + user.Username + "#" + user.Discriminator);
|
|
if (user.Nickname != null) result.Append(" - Nickname: " + user.Nickname);
|
|
result.AppendLine();
|
|
}
|
|
return result.ToString();
|
|
}
|
|
|
|
private string ListExportCsv(SocketGuildChannel channel, IEnumerable<ListItem> list)
|
|
{
|
|
// Output: User ID, Username, Nickname, Month-Day, Month, Day
|
|
var result = new StringBuilder();
|
|
|
|
// Conforming to RFC 4180; with header
|
|
result.Append("UserId,Username,Nickname,MonthDayDisp,Month,Day");
|
|
result.Append("\r\n"); // crlf line break is specified by the standard
|
|
foreach (var item in list)
|
|
{
|
|
var user = channel.Guild.GetUser(item.UserId);
|
|
if (user == null) continue; // User disappeared in the instant between getting list and processing
|
|
result.Append(item.UserId);
|
|
result.Append(',');
|
|
result.Append(CsvEscape(user.Username + "#" + user.Discriminator));
|
|
result.Append(',');
|
|
if (user.Nickname != null) result.Append(user.Nickname);
|
|
result.Append(',');
|
|
result.Append($"{Common.MonthNames[item.BirthMonth]}-{item.BirthDay.ToString("00")}");
|
|
result.Append(',');
|
|
result.Append(item.BirthMonth);
|
|
result.Append(',');
|
|
result.Append(item.BirthDay);
|
|
result.Append("\r\n");
|
|
}
|
|
return result.ToString();
|
|
}
|
|
|
|
private 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 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;
|
|
}
|
|
}
|
|
}
|