AFK Macro

Idle Mafia Macro/Source/src/linux/LinuxMain.cs

LinuxMain.cs

Linux: start-up · 445 lines · 22 KB

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
namespace IdleMafiaBot
{
static class LinuxMain
{
public static void Run(string[] args)
{
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Regex.CacheSize = 512;
string dir = AppDomain.CurrentDomain.BaseDirectory;
using (var one = OneOnly(dir))
{
if (one == null) { Console.WriteLine("The bot is already running here."); return; }
string ini = Path.Combine(dir, "IdleMafiaBot.ini");
var settings = Settings.Load(ini);
try { settings.Save(ini); } catch (Exception) { }
if (args.Contains("--ini")) return;
var preset = args.FirstOrDefault(a => a.StartsWith("--preset=", StringComparison.Ordinal));
if (preset != null && !LoadPreset(preset.Substring(9), settings, ini)) return;
string log = Path.Combine(dir, "IdleMafiaBot.log");
var lastLine = File.Exists(log) ? File.GetLastWriteTime(log) : DateTime.MinValue;
var bot = new Bot(settings, dir);
bot.Logged += Console.WriteLine;
var trust = Trust.Sync();
if (trust != null) bot.Log(trust);
string said = null;
bot.StateChanged += st =>
{
if (st == said) return;
said = st;
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " [" + st + "]");
};
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
var ex = e.ExceptionObject as Exception;
bot.LogDetails(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " Idle Mafia Bot stopped because of an error: " + (ex != null ? ex.ToString() : "" + e.ExceptionObject));
};
SayLastRun(bot, dir, lastLine);
RobloxWindow.Note = bot.Log;
string problem = Ocr.Check();
if (problem != null) { bot.Log("Problem: " + problem); return; }
bool once = args.Contains("--once"), scan = args.Contains("--scan"), check = args.Contains("--check");
exitAfter = args.Contains("--exit");
bot.Once = once;
bot.StartWatching();
string commands = Path.Combine(dir, "IdleMafiaBot.cmd");
Opened(dir);
WatchSignals(bot, dir, commands);
WireRemote(bot, settings, dir);
LinuxPage.Wire(bot, settings, dir, on => Hold(dir, on));
string gone = Path.Combine(dir, "stats-removed");
if (File.Exists(gone))
{
try
{
var olds = File.ReadAllLines(gone).Select(l => l.Trim()).Where(l => l.Length > 0).ToList();
File.Delete(gone);
string wid = (settings.StatsMessage ?? "").Trim().Split('/')[0];
string old = olds.FirstOrDefault(l => wid.Length > 0 && StatsCard.WebhookOf(l) == wid) ?? olds.FirstOrDefault();
if (old != null) { bot.Stats.Removed(old); bot.SaveSettings(); }
}
catch (Exception) { }
}
bot.Stats.Begin();
bot.Remote.Begin();
LinuxPages.Watch(bot, settings, dir);
if (scan) bot.RequestScan();
else if (check) bot.RequestCheck();
else if (!args.Contains("--stopped")) bot.Start();
Loop(bot, settings, dir, commands, once || scan || check || exitAfter);
}
}
static FileStream OneOnly(string dir)
{
try { return new FileStream(Path.Combine(dir, ".running"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); }
catch (IOException) { return null; }
}
static bool LoadPreset(string file, Settings settings, string ini)
{
string why;
var p = Preset.Parse(File.ReadAllText(file), out why);
if (p == null) { Console.WriteLine("That preset can't be used: " + why); return false; }
int skipped = p.ApplyTo(settings);
settings.Save(ini);
Console.WriteLine("Preset \"" + p.Name + "\" is in the settings" + (skipped > 0 ? " (" + skipped + " values left as they were)" : ""));
return true;
}
static void Loop(Bot bot, Settings settings, string dir, string commands, bool endWhenStopped)
{
string state = Path.Combine(dir, "IdleMafiaBot.state");
try { Turns(bot, settings, dir, commands, endWhenStopped, state); }
finally { try { File.Delete(state); } catch (Exception) { } }
}
static void Turns(Bot bot, Settings settings, string dir, string commands, bool endWhenStopped, string state)
{
bool seenRunning = false;
bool? said = null;
while (true)
{
if (bot.Running != said)
{
said = bot.Running;
try { File.WriteAllText(state, said == true ? "playing" : "stopped"); } catch (Exception) { }
}
Thread.Sleep(1000);
if (Thread.VolatileRead(ref closing) != 0) Thread.Sleep(Timeout.Infinite);
Quiet(dir, !bot.Running || bot.GameLeftToYou);
seenRunning |= bot.Running;
if (endWhenStopped && seenRunning && !bot.Running)
{
if (OneOffOver(bot, dir)) return;
endWhenStopped = false;
}
string changes = Path.Combine(dir, "IdleMafiaBot.set");
if (File.Exists(changes)) TakeSettings(bot, settings, dir, changes);
LinuxPage.Turn(bot, settings, dir);
string cmd = null;
try
{
if (File.Exists(commands)) { cmd = File.ReadAllText(commands).Trim().ToLowerInvariant(); File.Delete(commands); }
}
catch (IOException) { }
if (cmd == "start" && !bot.Running) bot.Start();
else if (cmd == "stop" && bot.Running) bot.Stop();
else if (cmd == "report") MakeReport(bot, dir, null);
else if (cmd == "exit" || cmd == "exit keep")
{
CloseByYou(bot, dir, cmd == "exit keep");
return;
}
}
}
static void TakeSettings(Bot bot, Settings s, string dir, string file)
{
string[] lines;
try { lines = File.ReadAllLines(file); }
catch (Exception) { return; }
string oldStats = (s.StatsLink ?? "").Trim(), oldLink = s.DiscordLink ?? "";
bool oldRemote = s.RemoteControl;
var unread = new List<string>();
foreach (var line in lines)
{
int eq = line.IndexOf('=');
if (eq <= 0) continue;
var field = typeof(Settings).GetField(line.Substring(0, eq).Trim());
if (field != null && !Settings.SetText(s, field, line.Substring(eq + 1).Trim())) unread.Add(field.Name);
}
string stats = (s.StatsLink ?? "").Trim();
if (stats != oldStats && oldStats.Length > 0) bot.Stats.Removed(oldStats);
bot.SaveSettings();
if ((s.DiscordLink ?? "") != oldLink) Accounts.ForgetLink(dir, s.Account);
if (stats != oldStats && stats.Length > 0) bot.Stats.Wake();
if (stats != oldStats || s.RemoteControl != oldRemote) bot.Remote.Wake();
try { File.Delete(file); } catch (Exception) { }
bot.Log("Settings saved from the Tasks page" + (unread.Count > 0 ? " (" + string.Join(", ", unread) + " couldn't be read: left as they were)" : ""));
}
static void WireRemote(Bot bot, Settings s, string dir)
{
bot.Remote.PresetNames = () => { try { return Preset.Load(dir).Select(p => p.Name).ToList(); } catch (Exception) { return new List<string>(); } };
bot.Remote.Shot = (out string why) => Discord.ShotOfRoblox(bot.Pinned, out why);
bot.Remote.ShareLost = text => bot.Log("Preset share: " + text);
bot.Remote.Handler = (what, arg, reply) =>
{
try { RemoteDo(bot, s, dir, what, arg, reply); }
catch (Exception e) { reply(false, "Something went wrong: " + e.Message); }
};
}
static void RemoteDo(Bot bot, Settings s, string dir, string what, string arg, Action<bool, string> reply)
{
switch (what)
{
case "start":
if (bot.Running) { reply(true, "It's already playing."); return; }
if (!new RobloxWindow().Find()) { reply(false, "Roblox isn't open on the PC, so the bot wasn't started. Open Idle Mafia Game there first, then send start again."); return; }
Hold(dir, false);
bot.Start();
reply(true, "Started. It plays now.");
return;
case "stop":
if (!bot.Running) { reply(true, "It's already stopped."); return; }
Hold(dir, true);
bot.Stop();
reply(true, "Stopped.");
return;
case "status":
{
string state = !bot.Running ? (new RobloxWindow().Find() ? "Stopped" : "Stopped (Roblox isn't open)") : bot.OutOfGame ? "Running, but the game is out of reach (disconnected or Roblox closed)" : "Playing";
var p = Path.Combine(dir, "problems", "active");
int n = Directory.Exists(p) ? Directory.GetFiles(p, "*.png").Length : 0;
var now = Preset.Load(dir).FirstOrDefault(x => x.Matches(s));
string numbers = bot.Stats.NumbersLine();
reply(true, state + "." + (numbers.Length > 0 ? "\n" + char.ToUpper(numbers[0]) + numbers.Substring(1) + "." : "")
+ "\nPreset: " + (now != null ? now.Name : "none of yours") + "."
+ "\nProblems: " + (n == 0 ? "none" : n.ToString(CultureInfo.InvariantCulture)) + "."
+ "\nVersion " + Program.Version + ".");
return;
}
case "preset":
{
var presets = Preset.Load(dir);
var p = presets.FirstOrDefault(x => Preset.SameName(x.Name, arg ?? ""));
if (p == null)
{
reply(false, "No preset called \"" + arg + "\"." + (presets.Count > 0 ? " Yours: " + string.Join(", ", presets.Select(x => x.Name)) + "." : " You have none yet: add one from a card in our Discord (Add to my bot)."));
return;
}
if (p.Matches(s, true)) { reply(true, "\"" + p.Name + "\" is already on."); return; }
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)" : ""));
reply(true, "Using \"" + p.Name + "\" now.");
return;
}
case "report":
MakeReport(bot, dir, reply);
return;
case "addpreset":
{
string why;
var p = Preset.Parse(arg, out why);
if (p == null) { reply(false, why); return; }
var presets = Preset.Load(dir);
int old = presets.FindIndex(o => Preset.SameName(o.Name, p.Name));
if (old >= 0) presets[old] = p; else presets.Add(p);
try { Preset.Save(dir, presets); }
catch (Exception) { reply(false, "The bot couldn't save its presets on the PC."); return; }
bot.Remote.Wake();
bot.Log("Preset added from Discord: " + p.Name);
reply(true, (old >= 0 ? "Replaced your \"" : "Added \"") + p.Name + "\" in your presets. Use it with /remote."
+ (p.Newer > 0 ? " " + p.Newer + " of its settings are from a newer bot: update yours to get them all." : ""));
return;
}
case "shared":
{
bool ok = arg.StartsWith("ok:", StringComparison.Ordinal);
string text = arg.Length > 3 ? arg.Substring(3) : arg;
bot.Log("Preset share: " + text);
reply(ok, text);
return;
}
}
reply(false, "The bot doesn't know that command.");
}
static void Hold(string dir, bool on)
{
try
{
string up = Path.GetDirectoryName(dir.TrimEnd('/', '\\'));
if (up == null || !File.Exists(Path.Combine(up, "keep.sh"))) return;
string hold = Path.Combine(up, "hold");
if (on) File.WriteAllText(hold, "");
else if (File.Exists(hold)) File.Delete(hold);
}
catch (Exception) { }
}
static bool? quietWas;
static void Quiet(string dir, bool on)
{
if (quietWas == on) return;
try
{
string quiet = Path.Combine(dir, "quiet");
if (on) File.WriteAllText(quiet, "");
else if (File.Exists(quiet)) File.Delete(quiet);
quietWas = on;
}
catch (Exception) { }
}
static bool exitAfter;
static int closing;
static void Close(Bot bot, string dir, string by, bool keepRoll, int waitSeconds, bool tell)
{
bot.Log("Closed by " + by);
bool wasRunning = bot.Running;
bot.Stop(keepRoll);
for (int i = 0; i < waitSeconds * 4 && bot.Running; i++) Thread.Sleep(250);
if (bot.Running && waitSeconds > 0) bot.Log("The round didn't end in " + waitSeconds + " s - closing anyway");
try { bot.FoldTotals(); } catch (Exception) { }
bool you = by.StartsWith("you", StringComparison.Ordinal) || by.StartsWith("--exit", StringComparison.Ordinal);
if (tell && wasRunning && !you)
bot.Discord.Problem("closed", "\U0001F6D1 The bot was closed", "By " + by + ".", Discord.ShotOfRoblox, false, 0);
bot.Discord.Flush(you ? 2000 : 4000);
bot.Stats.Closing(by);
try { File.Delete(OpenMarker(dir)); } catch (Exception) { }
}
static void CloseByYou(Bot bot, string dir, bool keep)
{
if (Interlocked.Exchange(ref closing, 1) != 0) Thread.Sleep(Timeout.Infinite);
Close(bot, dir, "you (bot.sh stop)", keep, 30, false);
}
static bool OneOffOver(Bot bot, string dir)
{
Hold(dir, true);
if (!exitAfter)
{
bot.Once = false;
bot.Log("The run is over: the bot stays open and stopped (Start on the Tasks page plays)");
return false;
}
if (Interlocked.Exchange(ref closing, 1) != 0) Thread.Sleep(Timeout.Infinite);
Close(bot, dir, "--exit (the run is over)", false, 0, true);
return true;
}
static void WatchSignals(Bot bot, string dir, string commands)
{
WaitHandle term;
try
{
var posix = System.Reflection.Assembly.Load("Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756");
term = (WaitHandle)Activator.CreateInstance(posix.GetType("Mono.Unix.UnixSignal", true), Enum.Parse(posix.GetType("Mono.Unix.Native.Signum", true), "SIGTERM"));
}
catch (Exception e)
{
bot.LogDetails(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " Linux's shutdown isn't watched (" + (e.InnerException ?? e).Message + "): a shutdown ends the bot without its closing lines");
return;
}
new Thread(() =>
{
bool got = false;
try { got = term.WaitOne(); }
catch (Exception) { }
if (got) Ended(bot, dir, commands);
else try { term.Close(); } catch (Exception) { }
}) { IsBackground = true, Name = "sigterm" }.Start();
}
static void Ended(Bot bot, string dir, string commands)
{
if (Interlocked.Exchange(ref closing, 1) != 0) { EndNow(); return; }
new Thread(() => { Thread.Sleep(20000); EndNow(); }) { IsBackground = true, Name = "sigterm limit" }.Start();
bool keepRoll, tell;
string by = SentBy(dir, commands, out keepRoll, out tell);
try { Close(bot, dir, by, keepRoll, 5, tell); }
catch (Exception) { }
try { File.Delete(Path.Combine(dir, "IdleMafiaBot.state")); } catch (Exception) { }
Environment.Exit(0);
}
static string SentBy(string dir, string commands, out bool keepRoll, out bool tell)
{
string cmd = "";
try { if (File.Exists(commands)) cmd = File.ReadAllText(commands).Trim().ToLowerInvariant(); }
catch (Exception) { }
keepRoll = true;
tell = false;
if (cmd == "exit" || cmd == "exit keep") { keepRoll = cmd == "exit keep"; return "you (bot.sh stop)"; }
string log = Path.Combine(dir, "IdleMafiaBot.log");
if (File.Exists(log) && !File.Exists(Path.Combine(dir, "quiet")) && DateTime.Now - File.GetLastWriteTime(log) > TimeSpan.FromMinutes(115))
return "keep.sh (no log line for 2 hours: it starts the game and the bot again)";
tell = true;
return "Linux (shutting down, restarting, signing out, or another program)";
}
static void EndNow()
{
try { System.Diagnostics.Process.GetCurrentProcess().Kill(); }
catch (Exception) { Environment.Exit(1); }
}
static string OpenMarker(string dir) { return Path.Combine(dir, "IdleMafiaBot.open"); }
static void Opened(string dir)
{
try { File.WriteAllText(OpenMarker(dir), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); }
catch (Exception) { }
}
static void SayLastRun(Bot bot, string dir, DateTime lastLine)
{
string open = OpenMarker(dir);
if (!File.Exists(open)) return;
try { File.Delete(open); } catch (Exception) { }
bot.Log("The last run ended without closing (the PC shut down or lost power, it was killed, or it stopped on an error)"
+ (lastLine > DateTime.MinValue ? " - its last log line was at " + lastLine.ToString("dd-MM HH:mm", CultureInfo.InvariantCulture) : ""));
}
static void MakeReport(Bot bot, string dir, Action<bool, string> reply)
{
string desktop = "";
try { desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) ?? ""; }
catch (Exception) { }
bool onDesktop = desktop.Length > 0 && Directory.Exists(desktop);
var before = DateTime.Now;
string zip;
int moved;
try
{
zip = ProblemReport.Write(dir, onDesktop ? desktop : dir, ProblemReport.RobloxWindowInfo(bot.Pinned));
moved = ProblemReport.MoveReported(dir, before);
}
catch (Exception e)
{
bot.Log("Couldn't make the report: " + e.Message);
if (reply != null) reply(false, "Couldn't make the report: " + e.Message);
return;
}
long size = new FileInfo(zip).Length;
string where = onDesktop ? "on your Desktop" : "in the bot's folder";
bot.Log(string.Format(CultureInfo.InvariantCulture, "Report saved {0}: {1} ({2}). {3}{4}", where + " (" + Home(Path.GetDirectoryName(zip)) + ")", Path.GetFileName(zip),
size < 1048576 ? Math.Max(1, size / 1024) + " KB" : (size / 1048576.0).ToString("0.0", CultureInfo.InvariantCulture) + " MB",
size > 10L * 1024 * 1024 ? "That's too big for Discord, so open an issue on the bot's GitHub page and add it there, with a few words about what went wrong."
: "Post it in the Discord with a few words about what went wrong.",
moved > 0 ? " Its " + moved + " problem picture" + (moved == 1 ? "" : "s") + " moved to problems/reported." : ""));
if (reply != null) bot.Remote.ReportZip = zip;
if (reply != null) reply(true, "Report saved " + where + ": " + Path.GetFileName(zip) + ". Post it in #bug-report when you're at your PC.");
}
static string Home(string folder)
{
string home = (Environment.GetEnvironmentVariable("HOME") ?? "").TrimEnd('/');
folder = (folder ?? "").TrimEnd('/');
return home.Length > 1 && (folder == home || folder.StartsWith(home + "/", StringComparison.Ordinal)) ? "~" + folder.Substring(home.Length) : folder;
}
}
}