-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using ClashOfClans.Models; | ||
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
Check warning on line 1 in Bot/Clash/ClanUtil.cs GitHub Actions / inspect
|
||
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 ClanUtil() | ||
{ | ||
_clan = new(); | ||
_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) | ||
{ | ||
ClanMember? clanMember = clan.MemberList!.FirstOrDefault(m => m.Tag == dbMember); | ||
if (clanMember == null) | ||
{ | ||
ClanMember m = new() | ||
{ | ||
Tag = dbMember | ||
}; | ||
c._members[dbMember] = m; // Fake a member | ||
} | ||
else | ||
{ | ||
c._members[dbMember] = clanMember; | ||
clan.MemberList!.Remove(clanMember); | ||
} | ||
} | ||
return c; | ||
} | ||
|
||
internal static ClanUtil FromPoll(Clan clan) | ||
{ | ||
ClanUtil c = new(clan, new(Coc.Clan._members)); | ||
foreach (ClanMember member in clan.MemberList!) | ||
{ | ||
c._members[member.Tag] = member; | ||
if (Coc.Clan.HasMember(member)) | ||
{ | ||
c._existingMembers[member.Tag] = member; | ||
c._leavingMembers.Remove(member.Tag); | ||
} | ||
else | ||
{ | ||
c._joiningMembers[member.Tag] = member; | ||
} | ||
} | ||
return c; | ||
} | ||
|
||
// ReSharper disable All | ||
private bool HasMember(ClanMember member) => _members.ContainsKey(member.Tag); | ||
// ReSharper enable All | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
using ClashOfClans; | ||
using ClashOfClans.Core; | ||
using ClashOfClans.Models; | ||
using Hyperstellar.Discord; | ||
using Hyperstellar.Sql; | ||
|
||
namespace Hyperstellar.Clash; | ||
|
||
internal static class Coc | ||
{ | ||
private const string ClanId = "#2QU2UCJJC"; // 2G8LP8PVV | ||
private static readonly ClashOfClansClient s_client = new(Secrets.s_coc); | ||
private static bool s_inMaintenance; | ||
internal static ClanUtil Clan { get; private set; } = new(); | ||
internal static event Action<ClanMember>? s_eventMemberJoined; | ||
internal static event Action<ClanMember, string?>? s_eventMemberLeft; | ||
internal static event Func<Dictionary<string, DonationTuple>, Task>? EventDonated; | ||
internal static event Func<Dictionary<string, DonationTuple>, Task>? EventDonatedFold; | ||
|
||
static Coc() => Dc.EventBotReady += BotReadyAsync; | ||
|
||
private static async Task BotReadyAsync() | ||
{ | ||
while (true) | ||
{ | ||
try | ||
{ | ||
await PollAsync(); | ||
s_inMaintenance = false; | ||
await Task.Delay(10000); | ||
} | ||
catch (ClashOfClansException ex) | ||
{ | ||
if (ex.Error.Reason == "inMaintenance") | ||
{ | ||
if (!s_inMaintenance) | ||
{ | ||
s_inMaintenance = true; | ||
await Dc.SendLogAsync(ex.Error.Message); | ||
} | ||
await Task.Delay(60000); | ||
} | ||
else | ||
{ | ||
await Dc.ExceptionAsync(ex); | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
await Dc.ExceptionAsync(ex); | ||
} | ||
} | ||
} | ||
|
||
private static void CheckMembersJoined(ClanUtil clan) | ||
{ | ||
if (clan._joiningMembers.Count == 0) | ||
{ | ||
return; | ||
} | ||
|
||
string[] members = [.. clan._joiningMembers.Keys]; | ||
Db.AddMembers(members); | ||
string membersMsg = string.Join(", ", members); | ||
Console.WriteLine($"{membersMsg} joined"); | ||
|
||
foreach (ClanMember m in clan._joiningMembers.Values) | ||
{ | ||
s_eventMemberJoined!(m); | ||
} | ||
} | ||
|
||
private static void CheckMembersLeft(ClanUtil clan) | ||
{ | ||
if (clan._leavingMembers.Count == 0) | ||
{ | ||
return; | ||
} | ||
|
||
foreach ((string id, ClanMember member) in clan._leavingMembers) | ||
{ | ||
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(); | ||
} | ||
s_eventMemberLeft!(member, altId); // This is before Db.DelMem below so that we can remap Donation to new mainId | ||
} | ||
|
||
string[] members = [.. clan._leavingMembers.Keys]; | ||
Db.DeleteMembers(members); | ||
string membersMsg = string.Join(", ", members); | ||
Console.WriteLine($"{membersMsg} left"); | ||
} | ||
|
||
private static async Task<Clan> GetClanAsync() => await s_client.Clans.GetClanAsync(ClanId); | ||
|
||
private static async Task PollAsync() | ||
{ | ||
Clan clan = await GetClanAsync(); | ||
|
||
if (clan.MemberList == null) | ||
{ | ||
return; | ||
} | ||
|
||
ClanUtil clanUtil = ClanUtil.FromPoll(clan); | ||
CheckMembersJoined(clanUtil); | ||
CheckMembersLeft(clanUtil); | ||
await Task.WhenAll([ | ||
CheckDonationsAsync(clanUtil) | ||
]); | ||
Clan = clanUtil; | ||
} | ||
|
||
private static async Task CheckDonationsAsync(ClanUtil clan) | ||
{ | ||
Dictionary<string, DonationTuple> donDelta = []; | ||
foreach (string tag in clan._existingMembers.Keys) | ||
{ | ||
ClanMember current = clan._members[tag]; | ||
ClanMember previous = Clan._members[tag]; | ||
if (current.Donations > previous.Donations || current.DonationsReceived > previous.DonationsReceived) | ||
{ | ||
donDelta[current.Tag] = new(current.Donations - previous.Donations, current.DonationsReceived - previous.DonationsReceived); | ||
} | ||
} | ||
|
||
foreach (KeyValuePair<string, DonationTuple> dd in donDelta) | ||
{ | ||
Console.WriteLine($"{dd.Key}: {dd.Value._donated} {dd.Value._received}"); | ||
} | ||
|
||
// Fold alt data into main | ||
Dictionary<string, DonationTuple> foldedDelta = []; | ||
foreach (KeyValuePair<string, DonationTuple> delta in donDelta) | ||
{ | ||
string tag = delta.Key; | ||
DonationTuple dt = delta.Value; | ||
Alt? alt = new Member(tag).TryToAlt(); | ||
if (alt != null) | ||
{ | ||
tag = alt.MainId; | ||
} | ||
foldedDelta[tag] = foldedDelta.TryGetValue(tag, out DonationTuple value) ? value.Add(dt) : dt; | ||
} | ||
|
||
if (foldedDelta.Count > 0) | ||
{ | ||
Console.WriteLine("---"); | ||
} | ||
|
||
foreach (KeyValuePair<string, DonationTuple> dd in foldedDelta) | ||
{ | ||
Console.WriteLine($"{dd.Key}: {dd.Value._donated} {dd.Value._received}"); | ||
} | ||
|
||
ICollection<Task> tasks = []; | ||
if (donDelta.Count > 0) | ||
{ | ||
tasks.Add(EventDonated!(donDelta)); | ||
} | ||
if (foldedDelta.Count > 0) | ||
{ | ||
tasks.Add(EventDonatedFold!(foldedDelta)); | ||
} | ||
await Task.WhenAll(tasks); | ||
} | ||
|
||
internal static string? GetMemberId(string name) | ||
{ | ||
ClanMember? result = Clan._clan.MemberList!.FirstOrDefault(m => m.Name == name); | ||
return result?.Tag; | ||
} | ||
|
||
internal static ClanMember GetMember(string id) => Clan._members[id]; | ||
|
||
internal static async Task InitAsync() => Clan = ClanUtil.FromInit(await GetClanAsync()); | ||
} |