AFK Macro

Idle Mafia Macro/Source/src/ProblemReport.cs

ProblemReport.cs

"Report a problem": a zip on your Desktop, never sent · 406 lines · 21 KB

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace IdleMafiaBot
{
static class ProblemReport
{
const long PictureBudget = 8L * 1024 * 1024, LogTail = 2L * 1024 * 1024, OldLogTail = 1L * 1024 * 1024;
#if LINUX
const long LinuxTail = 256L * 1024;
#endif
public static string Write(string appDir, string toDir, string roblox)
{
Directory.CreateDirectory(toDir);
string stamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture);
string zip = Path.Combine(toDir, "IdleMafiaBot-report-" + stamp + ".zip");
for (int n = 2; File.Exists(zip); n++) zip = Path.Combine(toDir, "IdleMafiaBot-report-" + stamp + "-" + n + ".zip");
var skipped = new List<string>();
var leftOut = new List<string>();
try
{
using (var z = new ZipArchive(new FileStream(zip, FileMode.CreateNew), ZipArchiveMode.Create))
{
AddText(z, Path.Combine(appDir, "IdleMafiaBot.log"), "IdleMafiaBot.log", LogTail, skipped, leftOut);
AddText(z, Path.Combine(appDir, "IdleMafiaBot.1.log"), "IdleMafiaBot.1.log", OldLogTail, skipped, leftOut);
AddText(z, Path.Combine(appDir, "jobs.txt"), "jobs.txt", 0, skipped, leftOut);
AddSettings(z, Path.Combine(appDir, "IdleMafiaBot.ini"), skipped);
foreach (var file in Directory.GetFiles(appDir, "profile-*.txt").OrderBy(f => f, StringComparer.OrdinalIgnoreCase))
AddText(z, file, Path.GetFileName(file), 0, skipped, leftOut);
AddProblems(z, Path.Combine(appDir, "problems"), skipped, leftOut);
#if LINUX
string up = Path.GetDirectoryName(appDir.TrimEnd('/', '\\'));
if (!string.IsNullOrEmpty(up))
{
AddText(z, Path.Combine(up, "keep.log"), "keep.log", LinuxTail, skipped, leftOut);
AddText(z, Path.Combine(up, "bot.out"), "bot.out", LinuxTail, skipped, leftOut);
}
#endif
using (var w = new StreamWriter(z.CreateEntry("system.txt").Open(), new UTF8Encoding(false)))
w.Write(NoUser(SystemText(roblox, skipped, leftOut)));
}
}
catch (Exception)
{
try { File.Delete(zip); } catch (Exception) { }
throw;
}
return zip;
}
public static int MoveReported(string appDir, DateTime before)
{
string active = Path.Combine(appDir, "problems", "active"), reported = Path.Combine(appDir, "problems", "reported");
if (!Directory.Exists(active)) return 0;
int moved = 0;
foreach (var file in Directory.GetFiles(active, "*", SearchOption.AllDirectories))
{
try
{
if (File.GetLastWriteTime(file) >= before) continue;
string rel = file.Substring(active.Length + 1), to = Path.Combine(reported, rel);
Directory.CreateDirectory(Path.GetDirectoryName(to));
for (int n = 2; File.Exists(to); n++)
to = Path.Combine(Path.GetDirectoryName(to), Path.GetFileNameWithoutExtension(rel) + "-" + n + Path.GetExtension(rel));
File.Move(file, to);
if (Path.GetDirectoryName(rel).Length == 0 && rel.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) moved++;
}
catch (Exception) { }
}
return moved;
}
static void AddProblems(ZipArchive z, string problems, List<string> skipped, List<string> leftOut)
{
string active = Path.Combine(problems, "active"), reported = Path.Combine(problems, "reported");
if (Directory.Exists(Path.Combine(problems, "fixed"))) leftOut.Add("problems/fixed (pictures of things already fixed)");
var files = new List<string>();
var older = new List<string>();
if (Directory.Exists(active))
files.AddRange(Directory.GetFiles(active, "*", SearchOption.AllDirectories).OrderByDescending(f => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase));
if (Directory.Exists(reported))
{
var all = Directory.GetFiles(reported, "*", SearchOption.AllDirectories);
var since = DateTime.Now.AddDays(-1);
var recent = all.Where(f => File.GetLastWriteTime(f) >= since).OrderByDescending(f => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase).ToList();
files.AddRange(recent);
older = all.Except(recent).OrderByDescending(f => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase).ToList();
if (older.Count > 0) leftOut.Add("problems/reported older than a day (pictures sent in an earlier report; of the last week the newest of each kind not seen since went in again, room allowing)");
}
if (files.Count == 0 && older.Count == 0) return;
var firsts = files.GroupBy(Kind, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
var kinds = new HashSet<string>(files.Select(Kind), StringComparer.OrdinalIgnoreCase);
var week = DateTime.Now.AddDays(-7);
var again = new HashSet<string>(older.Where(f => File.GetLastWriteTime(f) >= week && !kinds.Contains(Kind(f)))
.GroupBy(Kind, StringComparer.OrdinalIgnoreCase).Select(g => g.First()));
var order = firsts.Concat(again).Concat(files.Except(firsts)).ToList();
long used = 0;
var over = new List<string>();
int covered = 0;
foreach (var file in order)
{
bool sent = again.Contains(file);
string name = NoUser("problems/" + file.Substring(problems.Length + 1).Replace('\\', '/'), true);
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { AddFile(z, file, name, skipped); continue; }
if (IsCovered(file)) { covered++; continue; }
if (used >= PictureBudget) { if (!sent) over.Add(name); continue; }
byte[] jpg;
try { jpg = Discord.JpgOf(file); }
catch (Exception ex) { skipped.Add(name + ": " + ex.Message.Replace(file, Path.GetFileName(file))); continue; }
if (jpg == null) continue;
if (used > 0 && used + jpg.Length > PictureBudget) { if (!sent) { over.Add(name); used = PictureBudget; } continue; }
used += jpg.Length;
var e = z.CreateEntry(Path.ChangeExtension(name, ".jpg"), CompressionLevel.NoCompression);
Stamp(e, file);
using (var dst = e.Open()) dst.Write(jpg, 0, jpg.Length);
}
if (covered > 0) leftOut.Add(covered + " problem pictures taken with another window over Roblox (they could show it)");
if (over.Count > 0)
{
leftOut.Add(over.Count + " older problem pictures (to keep the zip small):");
foreach (var n in over.Take(100)) leftOut.Add(" " + n);
if (over.Count > 100) leftOut.Add(" and " + (over.Count - 100) + " more");
}
}
static string Kind(string file)
{
return Regex.Replace(Regex.Replace(Path.GetFileNameWithoutExtension(file), @"^\d{8}-\d{6}-", ""), @"-\d+$", "");
}
internal static bool IsCovered(string file)
{
return Regex.IsMatch(Path.GetFileNameWithoutExtension(file), Regex.Escape(Game.Covered) + @"(-\d+)?$", RegexOptions.IgnoreCase);
}
static void AddText(ZipArchive z, string file, string name, long tail, List<string> skipped, List<string> leftOut)
{
if (!File.Exists(file)) return;
try
{
byte[] bytes;
using (var src = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
long from = tail > 0 && src.Length > tail ? src.Length - tail : 0;
src.Seek(from, SeekOrigin.Begin);
using (var ms = new MemoryStream()) { src.CopyTo(ms); bytes = ms.ToArray(); }
if (from > 0)
{
int nl = Array.IndexOf(bytes, (byte)'\n');
if (nl >= 0) bytes = bytes.Skip(nl + 1).ToArray();
leftOut.Add(name + ": its first " + Size(from + (nl + 1)) + " (older lines)");
}
}
string text = NoUser(HideLink(new UTF8Encoding(false).GetString(bytes)));
var e = z.CreateEntry(name, CompressionLevel.Optimal);
Stamp(e, file);
using (var w = new StreamWriter(e.Open(), new UTF8Encoding(false))) w.Write(text);
}
catch (IOException ex) { skipped.Add(name + ": " + ex.Message.Replace(file, name)); }
catch (UnauthorizedAccessException ex) { skipped.Add(name + ": " + ex.Message.Replace(file, name)); }
}
static void AddFile(ZipArchive z, string file, string name, List<string> skipped)
{
if (!File.Exists(file)) return;
try
{
using (var src = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
var e = z.CreateEntry(name, CompressionLevel.Optimal);
Stamp(e, file);
using (var dst = e.Open()) src.CopyTo(dst);
}
}
catch (IOException ex) { skipped.Add(name + ": " + ex.Message.Replace(file, Path.GetFileName(file))); }
catch (UnauthorizedAccessException ex) { skipped.Add(name + ": " + ex.Message.Replace(file, Path.GetFileName(file))); }
}
static void Stamp(ZipArchiveEntry e, string file)
{
var time = File.GetLastWriteTime(file);
if (time.Year >= 1980 && time.Year <= 2107) e.LastWriteTime = time;
}
static string Size(long bytes)
{
return bytes < 1048576 ? Math.Max(1, bytes / 1024) + " KB" : (bytes / 1048576.0).ToString("0.0", CultureInfo.InvariantCulture) + " MB";
}
internal static string NoUser(string text, bool fileName = false)
{
if (string.IsNullOrEmpty(text)) return text ?? "";
string home = "", user = "";
try { home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? ""; user = Environment.UserName ?? ""; }
catch (Exception) { }
home = home.TrimEnd('\\', '/');
if (home.Length > 3)
{
text = Regex.Replace(text, Regex.Escape(home), "%USERPROFILE%", RegexOptions.IgnoreCase);
text = Regex.Replace(text, Regex.Escape(home.Replace('\\', '/')), "%USERPROFILE%", RegexOptions.IgnoreCase);
}
if (user.Length >= 3)
text = Regex.Replace(text, (fileName ? @"(?<=[\\/-])" : @"(?<=[\\/])") + Regex.Escape(user) + @"(?![A-Za-z0-9])", fileName ? "user" : "<user>", RegexOptions.IgnoreCase);
return text;
}
static void AddSettings(ZipArchive z, string file, List<string> skipped)
{
if (!File.Exists(file)) return;
try
{
string text;
using (var src = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
using (var r = new StreamReader(src)) text = r.ReadToEnd();
text = NoUser(HideLink(text));
using (var w = new StreamWriter(z.CreateEntry("IdleMafiaBot.ini").Open(), new UTF8Encoding(false))) w.Write(text);
}
catch (IOException ex) { skipped.Add("IdleMafiaBot.ini: " + ex.Message.Replace(file, "IdleMafiaBot.ini")); }
catch (UnauthorizedAccessException ex) { skipped.Add("IdleMafiaBot.ini: " + ex.Message.Replace(file, "IdleMafiaBot.ini")); }
}
internal static string HideLink(string text)
{
text = System.Text.RegularExpressions.Regex.Replace(text, @"(?m)^([ \t]*(?:DiscordLink|StatsLink|RobloxUsers)[ \t]*=)[ \t]*[^\s][^\r\n]*", "$1(set, left out of this report)");
text = System.Text.RegularExpressions.Regex.Replace(text, @"(your Roblox name is )@\S+", "$1(left out)");
return System.Text.RegularExpressions.Regex.Replace(text, @"https?://\S*webhooks/\S+", "(a webhook link, left out)");
}
static string SystemText(string roblox, List<string> skipped, List<string> leftOut)
{
var sb = new StringBuilder();
var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now);
sb.AppendLine("Idle Mafia Bot " + Program.Version);
sb.AppendLine("Report made: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
+ " (UTC" + (offset < TimeSpan.Zero ? "-" : "+") + offset.ToString(@"hh\:mm") + ", the log's clock)");
#if LINUX
sb.AppendLine("Linux: " + LinuxVersion());
#else
sb.AppendLine("Windows: " + WindowsVersion());
#endif
sb.AppendLine(".NET: " + Environment.Version + (Environment.Is64BitProcess ? ", 64-bit" : ", 32-bit"));
sb.AppendLine("Memory: " + Try(() => Native.MemoryText()));
#if !LINUX
sb.AppendLine("Monitors (real pixels, Windows scale):");
var monitors = Monitors();
for (int i = 0; i < monitors.Count; i++) sb.AppendLine(" " + (i + 1) + ": " + Describe(monitors[i]));
if (monitors.Count == 0) sb.AppendLine(" none found");
#endif
sb.AppendLine("Roblox window: " + roblox);
string ocr = Ocr.Check();
sb.AppendLine("Text recognition (OCR): " + (ocr == null ? Ocr.Language : "not usable - " + ocr));
#if !LINUX
sb.AppendLine(" installed on this PC: " + Try(() => string.Join(", ", Windows.Media.Ocr.OcrEngine.AvailableRecognizerLanguages.Select(l => l.LanguageTag))));
sb.AppendLine(" Windows languages: " + Try(() => string.Join(", ", Windows.System.UserProfile.GlobalizationPreferences.Languages)));
#endif
foreach (var s in skipped) sb.AppendLine("Couldn't add " + s);
if (leftOut.Count > 0) sb.AppendLine("Left out of this zip:");
foreach (var s in leftOut) sb.AppendLine(" " + s);
return sb.ToString();
}
static string Try(Func<string> f)
{
try { string s = f(); return s.Length > 0 ? s : "none"; }
catch (Exception e) { return "? (" + e.Message + ")"; }
}
#if LINUX
static string LinuxVersion()
{
string name = "Linux", kernel = "?";
try
{
var line = File.ReadAllLines("/etc/os-release").FirstOrDefault(l => l.StartsWith("PRETTY_NAME=", StringComparison.Ordinal));
if (line != null) name = line.Substring(12).Trim('"');
}
catch (Exception) { }
try { kernel = File.ReadAllText("/proc/sys/kernel/osrelease").Trim(); } catch (Exception) { }
return name + " (kernel " + kernel + ")";
}
public static string RobloxWindowInfo(IntPtr pinned = default(IntPtr))
{
try
{
var w = new RobloxWindow { Pinned = pinned };
if (!w.Find()) return "not found (Roblox isn't open)";
return w.Width + "x" + w.Height + (RobloxWindow.CoveredBy != null ? ", under " + RobloxWindow.CoveredBy : "");
}
catch (Exception e) { return "? (" + e.Message + ")"; }
}
#else
static string WindowsVersion()
{
try
{
using (var k = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
{
string name = k.GetValue("ProductName") as string ?? "Windows";
string release = k.GetValue("DisplayVersion") as string ?? k.GetValue("ReleaseId") as string;
string build = k.GetValue("CurrentBuild") as string ?? "?";
object ubr = k.GetValue("UBR");
int b;
if (int.TryParse(build, out b) && b >= 22000) name = name.Replace("Windows 10", "Windows 11");
return name + (release != null ? " " + release : "") + " (build " + build + (ubr != null ? "." + ubr : "") + ")"
+ (Environment.Is64BitOperatingSystem ? ", 64-bit" : ", 32-bit");
}
}
catch (Exception) { return Environment.OSVersion.ToString(); }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct MONITORINFOEX
{
public int cbSize;
public Native.RECT Monitor, Work;
public uint Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Device;
}
delegate bool MonitorProc(IntPtr monitor, IntPtr dc, IntPtr rect, IntPtr data);
[DllImport("user32.dll")] static extern bool EnumDisplayMonitors(IntPtr dc, IntPtr clip, MonitorProc proc, IntPtr data);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern bool GetMonitorInfo(IntPtr monitor, ref MONITORINFOEX info);
[DllImport("user32.dll")] static extern IntPtr MonitorFromWindow(IntPtr window, uint flags);
[DllImport("shcore.dll")] static extern int GetDpiForMonitor(IntPtr monitor, int type, out uint dpiX, out uint dpiY);
static List<IntPtr> Monitors()
{
var list = new List<IntPtr>();
try
{
MonitorProc add = (m, dc, r, d) => { list.Add(m); return true; };
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, add, IntPtr.Zero);
GC.KeepAlive(add);
}
catch (Exception) { }
return list;
}
static string Describe(IntPtr monitor)
{
try
{
var mi = new MONITORINFOEX { cbSize = Marshal.SizeOf(typeof(MONITORINFOEX)) };
if (!GetMonitorInfo(monitor, ref mi)) return "?";
uint dx, dy;
string scale = GetDpiForMonitor(monitor, 0, out dx, out dy) == 0 ? (dx * 100 / 96) + "%" : "?";
var r = mi.Monitor;
return string.Format(CultureInfo.InvariantCulture, "{0}x{1} at ({2},{3}), scale {4}{5}",
r.Right - r.Left, r.Bottom - r.Top, r.Left, r.Top, scale, (mi.Flags & 1) != 0 ? ", main" : "");
}
catch (Exception e) { return "? (" + e.Message + ")"; }
}
public static string RobloxWindowInfo(IntPtr pinned = default(IntPtr))
{
try
{
var w = new RobloxWindow { Pinned = pinned };
if (!w.Find()) return "not found (Roblox isn't open)";
if (w.Minimized) return "minimized";
return w.Width + "x" + w.Height + " client area, " + MonitorOf(w.Handle)
+ (RobloxWindow.ScreenFirst ? ", read from the screen (Windows gave empty pictures of it)" : "")
+ (RobloxWindow.CoveredBy != null ? ", under " + RobloxWindow.CoveredBy + " (no pictures to Discord)" : "")
+ "; open: " + string.Join(", ", RobloxWindow.All().Select(ClientRect));
}
catch (Exception e) { return "? (" + e.Message + ")"; }
}
static string ClientRect(IntPtr window)
{
Native.RECT r;
var p = new Native.POINT();
if (!Native.GetClientRect(window, out r) || !Native.ClientToScreen(window, ref p)) return "?";
return (r.Right - r.Left) + "x" + (r.Bottom - r.Top) + " at " + p.X + "," + p.Y;
}
internal static string MonitorOf(IntPtr window)
{
int i = Monitors().IndexOf(MonitorFromWindow(window, 2));
return i >= 0 ? "on monitor " + (i + 1) : "on no monitor";
}
#endif
}
}