Skip to content

Commit

Permalink
[Bot] Always save valid donation count
Browse files Browse the repository at this point in the history
  • Loading branch information
Pythonic-Rainbow committed Feb 3, 2024
1 parent bf64e3c commit dfa4d67
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 448 deletions.
1 change: 1 addition & 0 deletions Bot/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_diagnostic.CA1852.severity = suggestion

# C# files
[*.cs]
Expand Down
66 changes: 66 additions & 0 deletions Bot/Clash/ClanUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using ClashOfClans.Models;
using Hyperstellar.Sql;

namespace Hyperstellar.Clash;

internal sealed class ClanUtil
{
internal readonly Clan _clan;
internal readonly Dictionary<string, ClanMember> _members = [];
internal readonly Dictionary<string, ClanMember> _existingMembers = [];
internal readonly Dictionary<string, ClanMember> _joiningMembers = [];
internal readonly Dictionary<string, ClanMember> _leavingMembers;

private ClanUtil(Clan clan, Dictionary<string, ClanMember> leavingMembers)
{
_clan = clan;
_leavingMembers = leavingMembers;
}

internal static ClanUtil FromInit(Clan clan)
{
ClanUtil c = new(clan, []);
IEnumerable<string> existingMembers = Db.GetMembers().Select(m => m.CocId);
foreach (string dbMember in existingMembers)
{
bool stillExists = false;
foreach (ClanMember clanMember in clan.MemberList!)

Check notice on line 27 in Bot/Clash/ClanUtil.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator] Part of loop's body can be converted into LINQ-expression but another 'GetEnumerator' method will be used" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/ClanUtil.cs(27,884)
{
if (clanMember.Tag.Equals(dbMember))

Check notice on line 29 in Bot/Clash/ClanUtil.cs

View workflow job for this annotation

GitHub Actions / inspect

"[InvertIf] Invert 'if' statement to reduce nesting" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/ClanUtil.cs(29,966)
{
c._members[dbMember] = clanMember;
clan.MemberList.Remove(clanMember);
stillExists = true;
break;
}
}
if (!stillExists)
{
c._members[dbMember] = new(); // Fake a member

Check notice on line 39 in Bot/Clash/ClanUtil.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ArrangeObjectCreationWhenTypeNotEvident] Missing type specification" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/ClanUtil.cs(39,1314)
}
}
return c;
}

internal static ClanUtil FromPoll(Clan clan)
{
ClanUtil c = new(clan, new(Coc.s_clan._members));

Check notice on line 47 in Bot/Clash/ClanUtil.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ArrangeObjectCreationWhenTypeNotEvident] Missing type specification" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/ClanUtil.cs(47,1474)
foreach (ClanMember member in clan.MemberList!)
{
c._members[member.Tag] = member;
if (Coc.s_clan.HasMember(member))
{
c._existingMembers[member.Tag] = member;
c._leavingMembers.Remove(member.Tag);
}
else
{
c._joiningMembers[member.Tag] = member;
}
}
return c;
}

internal bool HasMember(ClanMember member) => _members.ContainsKey(member.Tag);

Check notice on line 64 in Bot/Clash/ClanUtil.cs

View workflow job for this annotation

GitHub Actions / inspect

"[MemberCanBePrivate.Global] Method 'HasMember' can be made private" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/ClanUtil.cs(64,1937)
}

204 changes: 204 additions & 0 deletions Bot/Clash/Coc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using ClashOfClans;
using ClashOfClans.Models;
using Hyperstellar.Sql;
using static Hyperstellar.Discord.Dc;

namespace Hyperstellar.Clash;

internal static class Coc
{

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"; // 2G8LP8PVV
private static readonly ClashOfClansClient s_client = new(Secrets.s_coc);
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal static ClanUtil s_clan { get; private set; }

Check warning on line 22 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[InconsistentNaming] Name 's_clan' does not match rule 'Properties'. Suggested name is 'Clan'." on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/Coc.cs(22,776)
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

private static void CheckMembersJoined(ClanUtil clan)
{
if (clan._joiningMembers.Count == 0)
{
return;
}

string[] members = [.. clan._joiningMembers.Keys];
bool isSuccess = Db.AddMembers(members);
string membersMsg = string.Join(", ", members);
if (isSuccess)
{
Console.WriteLine($"{membersMsg} joined");
}
else
{
Console.Error.WriteLine($"ERROR MembersJoined {membersMsg}");
}

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

private static void CheckMembersLeft(ClanUtil clan)
{
if (clan._leavingMembers.Count == 0)
{
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.MemberRemoved(id, altId);
}


string[] members = [.. clan._leavingMembers.Keys];
bool isSuccess = Db.DeleteMembers(members);
string membersMsg = string.Join(", ", members);
if (isSuccess)
{
Console.WriteLine($"{membersMsg} left");
}
else
{
Console.Error.WriteLine($"ERROR MembersLeft {membersMsg}");
}
}

private static async Task<Clan> GetClanAsync() => await s_client.Clans.GetClanAsync(ClanId);

private static async Task PollAsync()
{
Clan clan = await GetClanAsync();
if (clan == null)

Check warning on line 93 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract] Expression is always false according to nullable reference types' annotations" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/Coc.cs(93,2853)
{
return;
}

if (clan.MemberList == null)
{
return;
}

ClanUtil clanUtil = ClanUtil.FromPoll(clan);
CheckMembersJoined(clanUtil);
CheckMembersLeft(clanUtil);
await Task.WhenAll([
CheckDonationsAsync(clanUtil),

Check notice on line 107 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ArrangeTrailingCommaInMultilineLists] Remove trailing comma to conform to code style" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/Coc.cs(107,3183)
]);
s_clan = clanUtil;
}

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 120 in Bot/Clash/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/Clash/Coc.cs(120,3621)
{
donationsDelta[current.Name] = new(current.Donations - previous.Donations, current.DonationsReceived - previous.DonationsReceived);

Check notice on line 122 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

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

Check notice on line 123 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[ArrangeObjectCreationWhenTypeNotEvident] Missing type specification" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/Coc.cs(123,3934)
}

}


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)

Check notice on line 161 in Bot/Clash/Coc.cs

View workflow job for this annotation

GitHub Actions / inspect

"[UseDeconstruction] Use deconstruction" on /home/runner/work/Hyperstellar/Hyperstellar/Bot/Clash/Coc.cs(161,5202)
{
string tag = donDelta.Key;
DonationTuple dt = donDelta.Value;
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}");
Db.UpdateDonation(donation);
}
}

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

internal static string? GetMemberId(string name)
{
ClanMember? result = s_clan._clan.MemberList!.FirstOrDefault(m => m.Name == name);
return result?.Tag;
}

internal static ClanMember GetMember(string id) => s_clan._members[id];

internal static async Task InitAsync() => s_clan = ClanUtil.FromInit(await GetClanAsync());

internal static async Task BotReadyAsync()
{
Donate25.Init();
_ = Task.Run(Donate25.CheckAsync);
while (true)
{
await PollAsync();
await Task.Delay(20000);
}
}
}
Loading

0 comments on commit dfa4d67

Please sign in to comment.