using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; namespace IdleMafiaBot { static class LinuxPage { static readonly CultureInfo IC = CultureInfo.InvariantCulture; static readonly object gate = new object(); static Profile profile; static string profileOf; static volatile int lastLevel = -1; static bool onlyRun; static Action hold; static string written; static DateTime nextState = DateTime.MinValue; static List presets; static DateTime presetsAt; static volatile string shareSaid = "", shareStamp = ""; static volatile string checkDone = ""; public static void Wire(Bot bot, Settings s, string dir, Action holdFile) { hold = holdFile; foreach (var old in new[] { "IdleMafiaBot.page", "IdleMafiaBot.ask", "IdleMafiaBot.answer" }) try { File.Delete(Path.Combine(dir, old)); } catch (Exception) { } bot.HeaderRead += h => { if (h != null && h.Level > 0) lastLevel = h.Level; }; bot.ProfileRead += p => { lock (gate) { profile = p; profileOf = Profile.Key(p.Name); } checkDone = DateTime.UtcNow.Ticks.ToString(IC); nextState = DateTime.MinValue; }; bot.ProfileChanged += p => { lock (gate) if (profileOf == Profile.Key(p.Name)) profile = p; }; bot.Logged += line => { int at = line.IndexOf("Preset share: ", StringComparison.Ordinal); if (at < 0) return; shareSaid = line.Substring(at + 14); shareStamp = DateTime.UtcNow.Ticks.ToString(IC); nextState = DateTime.MinValue; }; } public static void Turn(Bot bot, Settings s, string dir) { if (bot.Running) { if (bot.FightOnly || bot.RerollOnly) onlyRun = true; } else if (onlyRun) { onlyRun = false; if (hold != null) hold(true); } string ask = Path.Combine(dir, "IdleMafiaBot.ask"); if (File.Exists(ask)) Answer(bot, s, dir, ask); var now = DateTime.UtcNow; if (now >= nextState) { nextState = now.AddSeconds(2); WriteState(bot, s, dir); } } static void Answer(Bot bot, Settings s, string dir, string file) { string text; try { text = File.ReadAllText(file); File.Delete(file); } catch (Exception) { return; } var part = text.Split(new[] { '\n' }, 3); string id = part[0].Trim(), what = part.Length > 1 ? part[1].Trim() : "", arg = part.Length > 2 ? part[2].TrimEnd('\r', '\n') : ""; string answer = Path.Combine(dir, "IdleMafiaBot.answer"); Action reply = json => Write(answer, "{\"id\":" + Q(id) + "," + json + "}"); try { Do(bot, s, dir, what, arg, reply); } catch (Exception e) { reply(Said(false, "Something went wrong: " + e.Message)); } nextState = DateTime.MinValue; } static void Do(Bot bot, Settings s, string dir, string what, string arg, Action reply) { switch (what) { case "fightonly": bot.StartFightOnly(); reply(Said(true, "Working: only fighting players")); return; case "rerollonly": bot.StartRerollOnly(); reply(Said(true, "Working: only rerolling the crew")); return; case "onlystop": if (arg == "fight" && bot.FightOnly) bot.StopFightOnly(); else if (arg == "reroll" && bot.RerollOnly) bot.StopRerollOnly(); else { reply(Said(true, "")); return; } reply(Said(true, "Stopping")); return; case "check": if (arg == "card") MarkOffered(bot, s); if (bot.Running) { reply(Said(false, "Stop the bot first, then press Check my game.")); return; } bot.RequestCheck(); reply(Said(true, "Checking")); return; case "checklater": lock (gate) { if (profile == null) MarkOffered(bot, s); else if (lastLevel > 0) { s.CheckHintLevel = lastLevel; bot.SaveSettings(); } } reply(Said(true, "")); return; case "review": { Focus f; if (arg.Length > 0 && Enum.TryParse(arg, out f) && Enum.IsDefined(typeof(Focus), f) && f != s.Focus) { s.Focus = f; bot.SaveSettings(); } reply(Review(bot, s)); return; } case "usecheck": { Profile p; lock (gate) p = profile; if (p == null) { reply(Said(false, "There's no check yet. Press Check my game.")); return; } var want = arg.Split(',').Select(x => x.Trim()).Where(x => x.Length > 0).ToList(); var chosen = Setup.Propose(p, s.Focus, s, bot.CatalogView).Where(pr => pr.Changes(s) && want.Contains(pr.Field)).ToList(); var said = chosen.Select(pr => pr.Name + " " + Setup.Show(pr.Field, pr.Value)).ToList(); foreach (var pr in chosen) typeof(Settings).GetField(pr.Field).SetValue(s, pr.Value); bot.SaveSettings(); bot.Log(chosen.Count == 0 ? "Kept your settings after the game check" : "Set up from your game check (" + s.Focus + " focus): " + string.Join(", ", said)); reply(Said(true, "") + ",\"fields\":" + Arr(chosen.Select(pr => pr.Field))); return; } case "keepcheck": bot.Log("Kept your settings after the game check"); reply(Said(true, "")); return; case "presets": reply(Said(true, "") + ",\"presets\":" + PresetList(bot, s, dir) + ",\"share\":" + B(bot.Remote.On)); return; case "presetsave": case "presetupdate": { string name = Preset.CleanName(arg); if (name.Length == 0) { reply(Said(false, "Type a name first.")); return; } var list = Presets(dir); var p = Preset.From(name, s); int old = list.FindIndex(o => Preset.SameName(o.Name, name)); if (old >= 0) list[old] = p; else list.Add(p); string why = SavePresets(bot, dir, list); if (why != null) { reply(Said(false, why)); return; } if (s.PresetInUse != p.Name) { s.PresetInUse = p.Name; bot.SaveSettings(); } bot.Log("Preset saved: " + p.Name); reply(Said(true, old >= 0 ? "\"" + p.Name + "\" now holds what's on." : "Saved \"" + p.Name + "\".") + ",\"presets\":" + PresetList(bot, s, dir)); return; } case "presetuse": { var p = Presets(dir).FirstOrDefault(o => Preset.SameName(o.Name, arg)); if (p == null) { reply(Said(false, "No preset called \"" + arg + "\".")); return; } reply(Use(bot, s, dir, p)); return; } case "presetdelete": { var list = Presets(dir); var p = list.FirstOrDefault(o => Preset.SameName(o.Name, arg)); if (p == null) { reply(Said(false, "No preset called \"" + arg + "\".")); return; } list.Remove(p); string why = SavePresets(bot, dir, list); reply(Said(why == null, why ?? "\"" + p.Name + "\" deleted.") + ",\"presets\":" + PresetList(bot, s, dir)); return; } case "presetshare": { var p = Presets(dir).FirstOrDefault(o => Preset.SameName(o.Name, arg)); if (p == null) { reply(Said(false, "No preset called \"" + arg + "\".")); return; } string why = bot.Remote.Share(p.Code()); if (why == "off") reply(Said(false, "To share with one click, switch on Stats in our Discord and Remote control from Discord (on this page, under Discord). Or press Copy code and type /preset share in our Discord.")); else if (why != null) reply(Said(false, "\"" + p.Name + "\" is too big to share this way. Press Copy code and type /preset share in our Discord.")); else { bot.Log("Preset: sharing " + p.Name + " in our Discord"); reply(Said(true, "Sharing \"" + p.Name + "\" in our Discord... (up to 15 seconds)")); } return; } case "presetcheck": { string why; var p = Preset.Parse(arg, out why); if (p == null) { reply(Said(false, why)); return; } bool taken = Presets(dir).Any(o => Preset.SameName(o.Name, p.Name)); reply(Said(true, "") + ",\"preset\":" + PresetJson(p, null) + ",\"taken\":" + B(taken)); return; } case "presetadd": case "presetadduse": { string why; var p = Preset.Parse(arg, out why); if (p == null) { reply(Said(false, why)); return; } var list = Presets(dir); int old = list.FindIndex(o => Preset.SameName(o.Name, p.Name)); if (old >= 0) list[old] = p; else list.Add(p); why = SavePresets(bot, dir, list); if (why != null) { reply(Said(false, why)); return; } bot.Log("Preset added from a code: " + p.Name); if (what == "presetadduse") reply(Use(bot, s, dir, p)); else reply(Said(true, "Added \"" + p.Name + "\".") + ",\"presets\":" + PresetList(bot, s, dir)); return; } case "discordtest": bot.Discord.Test(arg, Discord.ShotOfRoblox, why => { try { if (why.Length == 0) { reply(Said(true, "Sent. Look in your Discord channel.")); bot.Log("Discord: a test message sent"); } else reply(Said(false, why)); } catch (Exception) { } }); return; case "statspreview": reply(Said(true, "") + ",\"text\":" + Q(bot.Stats.Preview())); return; case "payers": reply(Payers(bot, Amount(s, arg))); return; case "payersfile": { long at = Amount(s, arg); var list = bot.Memory.BigPayers(at, DateTime.UtcNow); bot.Log("Fights: Big payers list saved (" + list.Count + " players)"); reply(Said(true, "") + ",\"text\":" + Q(PayersText(list, at, DateTime.UtcNow)) + ",\"file\":" + Q("Big payers " + DateTime.Now.ToString("dd-MM-yyyy HH.mm", IC) + ".txt")); return; } case "payerforget": if (bot.Memory.Forget(arg)) bot.Log("Fights: forgot " + arg + " (Big payers list)"); reply(Said(true, "")); return; } reply(Said(false, "The bot doesn't know that. Update it.")); } static void WriteState(Bot bot, Settings s, string dir) { string key = s.Account.Length > 0 ? Profile.Key(s.Account) : null; Profile p; string of; lock (gate) { if (key != null && key != profileOf) { profileOf = key; profile = Profile.Load(Profile.FileFor(dir, s.Account)); } p = profile; of = profileOf; } string when = p == null ? "Reads your game for about a minute and suggests settings. Nothing changes until you say so." : string.Format(IC, "Last check {0:dd-MM} at {0:HH:mm}{1}. Check again every few levels.", p.Checked, p.Level > 0 ? ", at level " + p.Level : ""); string title = null, card = null; if (of != null && !bot.Running) { if (p == null && !Offered(s, of)) { title = "Set the bot up for your account?"; card = "It reads your game for about a minute and suggests settings. Nothing changes until you say so."; } else if (p != null && lastLevel > 0) { card = Setup.RecheckHint(p, lastLevel, s.CheckHintLevel); title = "Check your game again?"; } } var now = Presets(dir).FirstOrDefault(x => x.Matches(s)); string only = bot.FightOnly ? "fight" : bot.RerollOnly ? "reroll" : ""; string json = "{\"only\":" + Q(only) + ",\"checking\":" + B(bot.Running && bot.CheckOrScan) + ",\"preset\":" + Q(now != null ? now.Name : "") + ",\"share\":" + B(bot.Remote.On) + ",\"shared\":" + Q(shareSaid) + ",\"sharedAt\":" + Q(shareStamp) + ",\"done\":" + Q(checkDone) + ",\"check\":{\"has\":" + B(p != null) + ",\"when\":" + Q(when) + ",\"title\":" + Q(card != null ? title : "") + ",\"text\":" + Q(card ?? "") + ",\"stamp\":" + Q(p != null ? p.Checked.ToString("yyyy-MM-dd HH:mm:ss", IC) : "") + "}}"; if (json == written) return; if (Write(Path.Combine(dir, "IdleMafiaBot.page"), json)) written = json; } static bool Offered(Settings s, string key) { return s.CheckOffered.Split(',').Any(n => n.Trim() == key); } static void MarkOffered(Bot bot, Settings s) { string of; lock (gate) of = profileOf; if (of == null || Offered(s, of)) return; s.CheckOffered = s.CheckOffered.Trim().Length == 0 ? of : s.CheckOffered.Trim() + "," + of; bot.SaveSettings(); } static string Review(Bot bot, Settings s) { Profile p; lock (gate) p = profile; if (p == null) return Said(false, "There's no check yet. Press Check my game."); string sub = (p.Name.Length > 0 ? p.Name : "Your account") + (p.Level > 0 ? ", level " + p.Level : "") + ", checked " + p.Checked.ToString("dd-MM", IC) + " at " + p.Checked.ToString("HH:mm", IC); var found = Setup.Found(p).Select(r => Arr(r)); var changes = new List(); var kept = new List(); foreach (var pr in Setup.Propose(p, s.Focus, s, bot.CatalogView)) { string own = Setup.OwnChoice(pr, s); string row = "{\"field\":" + Q(pr.Field) + ",\"name\":" + Q(pr.Name) + ",\"old\":" + Q(Setup.Show(pr.Field, pr.Current(s))) + ",\"new\":" + Q(Setup.Show(pr.Field, pr.Value)) + ",\"reason\":" + Q(pr.Reason) + ",\"note\":" + Q(own ?? "") + ",\"use\":" + B(own == null) + "}"; (pr.Changes(s) ? changes : kept).Add(row); } return Said(true, "") + ",\"sub\":" + Q(sub) + ",\"focus\":" + Q(s.Focus.ToString()) + ",\"focusHint\":" + Q(Setup.FocusHint(s.Focus)) + ",\"found\":[" + string.Join(",", found) + "],\"changes\":[" + string.Join(",", changes) + "],\"kept\":[" + string.Join(",", kept) + "]"; } static List Presets(string dir) { DateTime at; try { at = File.Exists(Preset.FileIn(dir)) ? File.GetLastWriteTimeUtc(Preset.FileIn(dir)) : DateTime.MinValue; } catch (Exception) { at = DateTime.MinValue; } if (presets == null || at != presetsAt) { presets = Preset.Load(dir); presetsAt = at; } return presets; } static string SavePresets(Bot bot, string dir, List list) { try { Preset.Save(dir, list); presets = null; bot.Remote.Wake(); return null; } catch (Exception e) { presets = null; bot.Log("Couldn't save the presets: " + e.Message); return "Couldn't save the presets: " + e.Message; } } static string Use(Bot bot, Settings s, string dir, Preset p) { int skipped = p.ApplyTo(s); s.PresetInUse = p.Name; bot.SaveSettings(); bot.Log("Preset: using " + p.Name + (skipped > 0 ? " (" + skipped + " settings left as they were)" : "")); return Said(true, "Using \"" + p.Name + "\"." + (skipped > 0 ? " " + skipped + " of its settings didn't fit this version and stay as they were." : "")) + ",\"preset\":" + PresetJson(p, null) + ",\"presets\":" + PresetList(bot, s, dir); } static string PresetList(Bot bot, Settings s, string dir) { return "[" + string.Join(",", Presets(dir).Select(p => PresetJson(p, s))) + "]"; } static string PresetJson(Preset p, Settings s) { var sb = new StringBuilder("{\"name\":").Append(Q(p.Name)).Append(",\"code\":").Append(Q(p.Code())).Append(",\"newer\":").Append(p.Newer.ToString(IC)); if (s != null) { bool on = p.Matches(s); sb.Append(",\"on\":").Append(B(on)).Append(",\"exact\":").Append(B(p.Matches(s, true))) .Append(",\"changed\":").Append(B(!on && Preset.SameName(p.Name, s.PresetInUse))); } sb.Append(",\"values\":{").Append(string.Join(",", p.Values.Select(v => Q(v.Key) + ":" + Q(v.Value)))).Append('}'); sb.Append(",\"missing\":").Append(Arr(Preset.Fields().Where(f => p.Value(f.Name) == null).Select(f => f.Name))).Append('}'); return sb.ToString(); } static long Amount(Settings s, string arg) { long v; return long.TryParse(arg, NumberStyles.Integer, IC, out v) && v > 0 ? v : s.BigPayerCash; } static string Payers(Bot bot, long at) { var m = bot.Memory; var now = DateTime.UtcNow; var rows = m.BigPayers(at, now).Select(p => "{\"name\":" + Q(p.Name) + ",\"biggest\":" + Q(SafehouseInfo.Money(p.Biggest)) + ",\"last\":" + Q(p.Last >= 0 ? SafehouseInfo.Money(p.Last) : "-") + ",\"when\":" + Q(Ago(p.LastBig, now)) + ",\"record\":" + Q(p.Won + "W " + p.Lost + "L") + ",\"tip\":" + Q(p.Fights + (p.Fights == 1 ? " fight" : " fights") + " in the last 3 days (the last 10 are kept): " + p.Won + " won, " + p.Lost + " lost") + "}"); int n = m.Count; return Said(true, "") + ",\"rows\":[" + string.Join(",", rows) + "],\"read\":" + Q(n == 0 ? "" : n + (n == 1 ? " player" : " players") + " remembered") + ",\"respect\":" + Q("Player fights written down: " + m.RespectSummary(now)); } static string PayersText(List list, long at, DateTime now) { var t = new StringBuilder(); t.AppendLine("Big payers: paid " + SafehouseInfo.Money(at) + " or more in a win, the last 3 days"); t.AppendLine("Saved " + DateTime.Now.ToString("dd-MM-yyyy HH:mm", IC) + " by Idle Mafia Bot"); t.AppendLine(); if (list.Count == 0) t.AppendLine("No player has paid that much in the last 3 days."); else { int w = Math.Max(6, list.Max(p => p.Name.Length)) + 2; t.AppendLine("PLAYER".PadRight(w) + "BIGGEST".PadLeft(10) + "LAST".PadLeft(10) + "LAST BIG ONE".PadLeft(16) + "FIGHTS".PadLeft(10)); foreach (var p in list) t.AppendLine(p.Name.PadRight(w) + SafehouseInfo.Money(p.Biggest).PadLeft(10) + (p.Last >= 0 ? SafehouseInfo.Money(p.Last) : "-").PadLeft(10) + Ago(p.LastBig, now).PadLeft(16) + (p.Won + "W " + p.Lost + "L").PadLeft(10)); } return t.ToString(); } static string Ago(DateTime utc, DateTime nowUtc) { var d = nowUtc - utc; if (d.TotalMinutes < 1) return "just now"; if (d.TotalMinutes < 60) return (int)d.TotalMinutes + " min ago"; if (d.TotalHours < 24) return (int)d.TotalHours + " h ago"; return utc.ToLocalTime().ToString("dd-MM HH:mm", IC); } static string Said(bool ok, string say) { return "\"ok\":" + B(ok) + ",\"say\":" + Q(say); } static string B(bool b) { return b ? "true" : "false"; } static string Arr(IEnumerable items) { return "[" + string.Join(",", items.Select(Q)) + "]"; } static string Q(string t) { var sb = new StringBuilder("\""); foreach (char c in t ?? "") { if (c == '"') sb.Append("\\\""); else if (c == '\\') sb.Append("\\\\"); else if (c == '\n') sb.Append("\\n"); else if (c == '\r') sb.Append("\\r"); else if (c == '\t') sb.Append("\\t"); else if (c < ' ' || c == (char)0x2028 || c == (char)0x2029) sb.Append("\\u").Append(((int)c).ToString("x4", IC)); else sb.Append(c); } return sb.Append('"').ToString(); } static bool Write(string file, string text) { string tmp = file + "." + Thread.CurrentThread.ManagedThreadId.ToString(IC) + ".tmp"; try { File.WriteAllText(tmp, text, new UTF8Encoding(false)); if (File.Exists(file)) File.Delete(file); File.Move(tmp, file); return true; } catch (Exception) { try { File.Delete(tmp); } catch (Exception) { } return false; } } } }