Skip to content

Commit

Permalink
[Bot] Add Donate25 (unoptimised)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pythonic-Rainbow committed Feb 2, 2024
1 parent 228e061 commit bf64e3c
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 21 deletions.
3 changes: 2 additions & 1 deletion Bot/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ dotnet_style_qualification_for_event = false:suggestion

# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_when_type_is_apparent = false:suggestion
csharp_style_var_elsewhere = false:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
Expand Down Expand Up @@ -177,6 +177,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0010.severity = none
csharp_style_deconstructed_variable_declaration = true:suggestion

# C++ Files
[*.{cpp,h,in}]
Expand Down
247 changes: 237 additions & 10 deletions Bot/Coc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,24 @@ internal static ClanUtil FromPoll(Clan clan)
internal bool HasMember(ClanMember member) => _members.ContainsKey(member.Tag);

Check notice on line 68 in Bot/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MemberCanBePrivate.Local] Method 'HasMember' can be made private" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Coc.cs(68,2228)

Check notice on line 68 in Bot/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[SuggestBaseTypeForParameter] Parameter can be of type 'ClashOfClans.Models.Identity'" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Coc.cs(68,2252)
}

private sealed class Donate25Node(long time)
{
internal long _checkTime = time;
internal ICollection<string> _ids = [];

Check notice on line 74 in Bot/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[FieldCanBeMadeReadOnly.Local] Field can be made readonly" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Coc.cs(74,2448)
}

internal readonly struct DonationTuple(int donated, int received)
{
internal readonly int _donated = donated;
internal readonly int _received = received;

internal DonationTuple Add(DonationTuple dt) => new(_donated + dt._donated, _received + dt._received);
}

private const string ClanId = "#2QU2UCJJC";
private const string ClanId = "#2QU2UCJJC"; // 2G8LP8PVV
private static readonly ClashOfClansClient s_client = new(Secrets.s_coc);
private static readonly HashSet<string> s_donate25Members = []; // Members to track donation
private static readonly Queue<Donate25Node> s_donate25Queue = []; // Queue for the await task
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private static ClanUtil s_clan;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
Expand All @@ -98,6 +108,11 @@ private static void CheckMembersJoined(ClanUtil clan)
{
Console.Error.WriteLine($"ERROR MembersJoined {membersMsg}");
}

foreach (string id in clan._joiningMembers.Keys)
{
Donate25_AddMember(id);
}
}

private static void CheckMembersLeft(ClanUtil clan)
Expand All @@ -107,8 +122,26 @@ private static void CheckMembersLeft(ClanUtil clan)
return;
}

foreach (string id in clan._leavingMembers.Keys)
{
IEnumerable<Alt> alts = new Member(id).GetAltsByMain();
string? altId = null;
if (alts.Any())
{
Alt alt = alts.First();
altId = alt.AltId;
for (int i = 1; i < alts.Count(); i++)
{
alts.ElementAt(i).UpdateMain(alt.AltId);
}
alt.Delete();
}
Donate25_RemoveMember(id, altId);
}


string[] members = [.. clan._leavingMembers.Keys];
bool isSuccess = Db.RemoveMembers(members);
bool isSuccess = Db.DeleteMembers(members);
string membersMsg = string.Join(", ", members);
if (isSuccess)
{
Expand Down Expand Up @@ -147,32 +180,225 @@ await Task.WhenAll([
private static async Task CheckDonationsAsync(ClanUtil clan)
{
Dictionary<string, DonationTuple> donationsDelta = [];
Dictionary<string, DonationTuple> donationsTagDelta = [];
foreach (string tag in clan._existingMembers.Keys)
{
ClanMember current = clan._members[tag];
ClanMember previous = s_clan._members[tag];
if (current.Donations > previous.Donations || current.DonationsReceived > previous.DonationsReceived)

Check notice on line 188 in Bot/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[InvertIf] Invert 'if' statement to reduce nesting" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Coc.cs(188,6089)
{
donationsDelta[current.Name] = new(current.Donations - previous.Donations, current.DonationsReceived - previous.DonationsReceived);

Check notice on line 190 in Bot/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ArrangeObjectCreationWhenTypeNotEvident] Missing type specification" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Coc.cs(190,6252)
donationsTagDelta[current.Tag] = new(current.Donations - previous.Donations, current.DonationsReceived - previous.DonationsReceived);
}

}


foreach (KeyValuePair<string, DonationTuple> dd in donationsTagDelta)
{
Console.WriteLine($"{dd.Key}: {dd.Value._donated} {dd.Value._received}");
}

// Fold alt data into main
Dictionary<string, DonationTuple> folded = [];
foreach (KeyValuePair<string, DonationTuple> donDelta in donationsTagDelta)
{
string tag = donDelta.Key;
DonationTuple dt = donDelta.Value;
Member member = new(tag);
Alt? alt = member.TryToAlt();
if (alt != null)
{
tag = alt.MainId;
}
folded[tag] = folded.TryGetValue(tag, out DonationTuple value) ? value.Add(dt) : dt;
}
donationsTagDelta = folded;

if (donationsTagDelta.Count > 0)
{
Console.WriteLine("---");
}

foreach (KeyValuePair<string, DonationTuple> dd in donationsTagDelta)
{
Console.WriteLine($"{dd.Key}: {dd.Value._donated} {dd.Value._received}");
}

// Everyone is main now
foreach (KeyValuePair<string, DonationTuple> donDelta in donationsTagDelta)
{
string tag = donDelta.Key;
DonationTuple dt = donDelta.Value;
if (s_donate25Members.Contains(tag)) // Member is tracked
{
IEnumerable<Alt> alts = new Member(tag).GetAltsByMain();
int altCount = alts.Count();
int donationTarget = 25 * (altCount + 1);
int donated = dt._donated;
int received = dt._received;

if (donated > received)
{
donated -= received;
Donation donation = Db.GetDonation(tag)!;
donation.Donated += (uint)donated;
Console.WriteLine($"[Donate25] {tag} {donated}");
if (donation.Donated >= donationTarget)
{
Console.WriteLine($"[Donate25] {tag} donated >{donationTarget}, removing from set");
s_donate25Members.Remove(tag);
}
Db.UpdateDonation(donation);
}
}
}

if (donationsDelta.Count > 0)
{
await DonationsChangedAsync(donationsDelta);
}
}

private static void Donate25_AddMember(string id)
{
Console.WriteLine($"[Donate25] Adding {id}");
s_donate25Members.Add(id);
long targetTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds();
Donate25Node node = s_donate25Queue.Last(); // We expect at least 1 member in the db
if (targetTime == node._checkTime)
{
node._ids.Add(id);
Console.WriteLine($"[Donate25] Added {id} in {node._checkTime} (last node)");
}
else if (targetTime > node._checkTime)
{
node = new(targetTime);
node._ids.Add(id);
s_donate25Queue.Enqueue(node);
Console.WriteLine($"[Donate25] Added {id} in {node._checkTime} (new node). New queue len: {s_donate25Queue.Count}");
}
else
{
Task.Run(() => WarnAsync($"This shouldn't happen: Donate25 new member targetTime < last node check time. " +
$"targetTime: {targetTime} Last node checktime: {node._checkTime}"));
}
}

internal static void Donate25_RemoveMember(string id, string? newMainId)
{
Console.WriteLine($"[Donate25] Removing {id} -> {newMainId}");
s_donate25Members.Remove(id);
foreach (Donate25Node node in s_donate25Queue)
{
if (node._ids.Remove(id))
{
Console.WriteLine($"[Donate25] Removed {id} in {node._checkTime}");
if (newMainId != null)
{
s_donate25Members.Add(newMainId);
node._ids.Add(newMainId);
Donation donation = Db.GetDonation(id)!;
donation.Delete();
donation.MainId = newMainId;
donation.Insert();
Console.WriteLine($"[Donate25] Added {newMainId} because it replaced {id} as main");
}
break;
}
}
}

internal static void Donate25_AddAlt(string altId, string mainId)
{
Console.WriteLine($"[Donate25] Removing {altId} -> {mainId} (addalt)");
s_donate25Members.Remove(altId);
foreach (Donate25Node node in s_donate25Queue)
{
if (node._ids.Remove(altId))
{
Console.WriteLine($"[Donate25] Removed {altId} in {node._checkTime}");
s_donate25Members.Add(mainId);
node._ids.Add(mainId);
Donation altDon = Db.GetDonation(altId)!;
Donation mainDon = Db.GetDonation(mainId)!;
altDon.Delete();
mainDon.Donated += altDon.Donated;
mainDon.Update();
Console.WriteLine($"[Donate25] Added {mainId} because it replaced {altId} as main");
break;
}
}
}

private static async Task Donate25Async()
{
while (true)
while (s_donate25Queue.Count > 0)
{
Donate25Node node = s_donate25Queue.First();
if (node._ids.Count > 0)
{
int waitDelay = (int)((node._checkTime * 1000) - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
await Task.Delay(waitDelay);
node = s_donate25Queue.Dequeue();
List<string> violators = [];
foreach (string member in node._ids)
{
if (s_donate25Members.Add(member))
{
Console.WriteLine($"[Donate25] {member} new cycle");
}
else
{
violators.Add(member);
Console.WriteLine($"[Donate25] {member} violated");
}
Donation donation = new(member, node._checkTime + (long)TimeSpan.FromDays(7).TotalSeconds);
Db.UpdateDonation(donation);
}

if (node._ids.Count > 0)
{
node._checkTime += (long)TimeSpan.FromDays(7).TotalSeconds;
s_donate25Queue.Enqueue(node);
}

if (violators.Count > 0)
{
_ = Task.Run(() => Donate25TriggerAsync(violators));
}
}
else
{
s_donate25Queue.Dequeue();
}
}
}

private static void InitDonate25Async()
{
IEnumerable<IGrouping<long, Donation>> donationGroups = Db.GetDonations()
.Where(d => s_clan._members.ContainsKey(d.MainId)) // Maybe can remove
.GroupBy(d => d.Checked)
.OrderBy(g => g.Key);

// Init donate25 vars
foreach (IGrouping<long, Donation> group in donationGroups)
{
DateTime now = DateTime.Now;
DateTime nextMonday = now.AddDays(((int)DayOfWeek.Monday - (int)now.DayOfWeek + 7) % 7)
.Date;
TimeSpan timeToWait = nextMonday - now;
await Task.Delay(timeToWait);
DateTimeOffset checkedTime = DateTimeOffset.FromUnixTimeSeconds(group.Key);
DateTimeOffset now = DateTimeOffset.UtcNow;
TimeSpan timePassed = now - checkedTime;

DateTimeOffset startingInstant = timePassed.Days >= 7 ? now : checkedTime;
long targetTime = startingInstant.AddDays(7).ToUnixTimeSeconds();

Donate25Node node = new(targetTime);
foreach (Donation donation in group)
{
node._ids.Add(donation.MainId);
s_donate25Members.Add(donation.MainId); // Add to set so OnDonate will start tracking them
}
s_donate25Queue.Enqueue(node);
}
}

Expand All @@ -188,11 +414,12 @@ private static async Task Donate25Async()

internal static async Task BotReadyAsync()
{
//_ = Task.Run(Donate25Async);
InitDonate25Async();
_ = Task.Run(Donate25Async);
while (true)
{
await PollAsync();
await Task.Delay(5000);
await Task.Delay(20000);
}
}
}
2 changes: 1 addition & 1 deletion Bot/Dc/Cmds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public async Task AltAsync(Member alt, Member main)
main.AddAlt(alt);
ClanMember clanAlt = Coc.GetMember(alt.CocId);
ClanMember clanMain = Coc.GetMember(main.CocId);
await RespondAsync($"`{clanAlt.Name}` is an alt of `{clanMain.Name}`");
await RespondAsync($"`{clanAlt.Name}` is now an alt of `{clanMain.Name}`");
}


Expand Down
11 changes: 11 additions & 0 deletions Bot/Dc/Discord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ private static async Task InteractionXAsync(ICommandInfo info, IInteractionConte
}
}

private static Task DisconnectedAsync(Exception ex)
{
Console.WriteLine("AYO");
throw ex;
}

internal static async Task InitAsync()
{
s_interactionSvc = new(s_bot);
s_bot.Log += Log;
s_bot.Ready += Ready;
s_bot.Disconnected += DisconnectedAsync;
s_bot.SlashCommandExecuted += SlashCmdXAsync;
s_interactionSvc.InteractionExecuted += InteractionXAsync;

Expand Down Expand Up @@ -85,4 +92,8 @@ internal static async Task DonationsChangedAsync(Dictionary<string, DonationTupl
msg += string.Join(", ", items);
await s_botLog.SendMessageAsync(msg);
}

internal static async Task Donate25TriggerAsync(List<string> violators) => await s_botLog.SendMessageAsync($"[Donate25] {string.Join(", ", violators)}");

internal static async Task WarnAsync(string msg) => await s_botLog.SendMessageAsync(msg);
}
2 changes: 1 addition & 1 deletion Bot/Dc/MemberConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Hyperstellar.Dc;

internal sealed class MemberConverter : TypeConverter
{
public override bool CanConvertTo(System.Type type) => typeof(Member).IsAssignableFrom(type);
public override bool CanConvertTo(Type type) => typeof(Member).IsAssignableFrom(type);
public override ApplicationCommandOptionType GetDiscordType() => ApplicationCommandOptionType.String;
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, IApplicationCommandInteractionDataOption option, IServiceProvider services)
{
Expand Down
12 changes: 11 additions & 1 deletion Bot/Sql/Alt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Hyperstellar.Sql;

internal sealed class Alt(string altId, string mainId)
public sealed class Alt(string altId, string mainId)
{
[PrimaryKey, NotNull]
public string AltId { get; set; } = altId;
Expand All @@ -11,4 +11,14 @@ internal sealed class Alt(string altId, string mainId)
public string MainId { get; set; } = mainId;

public Alt() : this("", "") { }

public TableQuery<Alt> GetOtherAlts() => Db.s_db.Table<Alt>().Where(a => a.MainId == MainId && a.AltId != AltId);

public bool UpdateMain(string id)
{
MainId = id;
return Db.s_db.Update(this) == 1;
}

public bool Delete() => Db.s_db.Delete(this) == 1;
}
Loading

0 comments on commit bf64e3c

Please sign in to comment.