Advertisement
apieceoffruit

Publish To Itch Menu

Apr 7th, 2025 (edited)
251
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 6.25 KB | None | 0 0
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Text.RegularExpressions;
  5. using System.Threading.Tasks;
  6. using UnityEditor;
  7. namespace JS
  8. {
  9.  
  10.     public static class Itch
  11.     {
  12.         public static string ButlerPath = "[YOUR BUTLER INSTALL PATH]\\butler.exe";
  13.  
  14.         public static Task PushBuildsFolder(ItchConfig config)
  15.         {
  16.             var builds = Path.Combine(UnityEngine.Application.dataPath, "../", "Builds");
  17.             return Push(config, builds);
  18.         }
  19.  
  20.         public static Task Push(ItchConfig config, string directory)
  21.         {
  22.             var reporter = new ItchUnityProgressReporter("Uploading to Itch:");
  23.             return Push(directory, config.user, config.game, config.channel, reporter);
  24.         }
  25.  
  26.         public static Task<int> Push(string directory, string user, string game, string channel,
  27.             IProgress<ButlerProgressInfo> progress = null) =>
  28.             ExecuteButlerAsync($"push {directory} {user}/{game}:{channel}", progress);
  29.  
  30.         static async Task<int> ExecuteButlerAsync(string arguments, IProgress<ButlerProgressInfo> progress = null,
  31.             string workingDirectory = null)
  32.         {
  33.             if (arguments == null)
  34.                 throw new ArgumentNullException(nameof(arguments));
  35.  
  36.             var startInfo = new ProcessStartInfo
  37.             {
  38.                 FileName = ButlerPath,
  39.                 Arguments = arguments,
  40.                 RedirectStandardOutput = true,
  41.                 RedirectStandardError = true,
  42.                 UseShellExecute = false,
  43.                 CreateNoWindow = true,
  44.                 WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory
  45.             };
  46.  
  47.             using var process = new Process();
  48.             process.StartInfo = startInfo;
  49.             process.EnableRaisingEvents = true;
  50.             process.OutputDataReceived += (sender, e) =>
  51.             {
  52.                 if (string.IsNullOrEmpty(e.Data)) return;
  53.                 ButlerProgressInfo progressInfo = ParseButlerOutput(e.Data);
  54.                 progress?.Report(progressInfo);
  55.             };
  56.  
  57.             process.ErrorDataReceived += (sender, e) =>
  58.             {
  59.                 if (!string.IsNullOrEmpty(e.Data))
  60.                     progress?.Report(new ButlerProgressInfo { Status = $"ERROR: {e.Data}", Percent = -1 });
  61.             };
  62.  
  63.             try
  64.             {
  65.                 process.Start();
  66.                 process.BeginOutputReadLine();
  67.                 process.BeginErrorReadLine();
  68.                 await Task.Run(() => process.WaitForExit());
  69.                 if (process.HasExited)
  70.                 {
  71.                     int exitCode = process.ExitCode;
  72.                     if (progress is ItchUnityProgressReporter rp)
  73.                     {
  74.                         if (exitCode == 0) rp.Complete();
  75.                         else rp.Fail();
  76.                     }
  77.                     if (exitCode != 0)
  78.                         throw new ButlerExecutionException($"Butler process exited with code {exitCode}");
  79.                     return exitCode;
  80.                 }
  81.                 UnityEngine.Debug.LogWarning("process did not exit within timeout");
  82.                 return -1;
  83.             }
  84.             catch (Exception ex)
  85.             {
  86.                 UnityEngine.Debug.LogError($"Error executing butler: {ex.Message}");
  87.                 return -1;
  88.             }
  89.         }
  90.  
  91.         static ButlerProgressInfo ParseButlerOutput(string output)
  92.         {
  93.             ButlerProgressInfo progressInfo = new ButlerProgressInfo();
  94.  
  95.             var match = DownloadProgressRegex.Match(output);
  96.             if (match.Success)
  97.             {
  98.                 if (float.TryParse(match.Groups[1].Value, out float percent))
  99.                 {
  100.                     if (percent > 0)
  101.                         progressInfo.Percent = percent / 100f;
  102.                     else progressInfo.Percent = -1;
  103.                 }
  104.  
  105.                 if(double.TryParse(match.Groups[3].Value, out double speed))
  106.                 {
  107.                     progressInfo.SpeedMiBs = speed;
  108.                 }
  109.  
  110.                 string remainingMiBsString = match.Groups[5].Value;
  111.                 if (double.TryParse(remainingMiBsString, out double remainingMiBs)) {
  112.  
  113.                     progressInfo.RemainingMiBs = remainingMiBs;
  114.                 }
  115.  
  116.                 progressInfo.Status = $"Uploading - {progressInfo.Percent:F2}% @ {progressInfo.SpeedMiBs:F2} MiB/s, {progressInfo.RemainingMiBs:F2} MiB left";
  117.             }
  118.             else { //If no download progress, then try to extract a generic status
  119.                 var statusMatch = GenericStatusRegex.Match(output);
  120.                 if (statusMatch.Success)
  121.                 {
  122.                     progressInfo.Status = statusMatch.Groups[1].Value.Trim();
  123.                 }
  124.             }
  125.  
  126.             return progressInfo;
  127.         }
  128.  
  129.         public class ButlerExecutionException : Exception
  130.         {
  131.             public ButlerExecutionException(string message) : base(message)
  132.             {
  133.             }
  134.         }
  135.  
  136.         private static readonly Regex DownloadProgressRegex = new(@"(\d+(?:\.\d+)?)%\s+@\s+((\d+(?:\.\d+)?)\s+MiB/s),\s+((\d+(?:\.\d+)?)\s+MiB) left", RegexOptions.Compiled);
  137.         private static readonly Regex GenericStatusRegex = new(@"ÔêÖ.*?\s+(.*)", RegexOptions.Compiled);
  138.  
  139.  
  140.     }
  141.  
  142.     [Serializable]
  143.     public class ItchConfig
  144.     {
  145.         public string user;
  146.         public string game;
  147.         public string channel = "win";
  148.     }
  149.  
  150.     [Serializable]
  151.     public class ButlerProgressInfo
  152.     {
  153.         public string Status;
  154.         public float Percent = -1;
  155.         public double SpeedMiBs;
  156.         public double RemainingMiBs;
  157.  
  158.         public override string ToString() => $"{Status} : {Percent} : {SpeedMiBs} : {RemainingMiBs}";
  159.     }
  160.  
  161.     public class ItchUnityProgressReporter : IProgress<ButlerProgressInfo>
  162.     {
  163.         private readonly int _id;
  164.         public ItchUnityProgressReporter(string title) => _id = Progress.Start(title);
  165.         public void Report(ButlerProgressInfo value) => Progress.Report(_id, value.Percent, value.Status);
  166.         public void Complete() => Progress.Finish(_id);
  167.  
  168.         public void Fail() => Progress.Finish(_id, Progress.Status.Failed);
  169.     }
  170. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement