Decompiled source of spooktube v1.1.3

spook.tube.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Photon.Pun;
using TMPro;
using ThunderstoreAPI;
using UnityEngine;
using UnityEngine.Localization.PropertyVariants;
using UnityEngine.UI;
using UnityEngine.UIElements.Collections;
using Zorro.Core;
using spook.tube;
using spook.tube.Uploaders;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("spook.tube")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("spook.tube")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("spook.tube")]
[assembly: AssemblyTitle("spook.tube")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
[HarmonyPatch(typeof(UploadCompleteUI), "PlayVideo")]
internal class VideoEndedUIInjection
{
	[HarmonyPostfix]
	private static void Postfix(UploadCompleteUI __instance)
	{
		Plugin.CreateUploadButton();
	}
}
namespace spook.tube
{
	internal class HTTPServer
	{
		private Thread thread;

		private HttpListener listener;

		private int port;

		public HTTPServer(int port)
		{
			this.port = port;
		}

		public void Start()
		{
			thread = new Thread((ThreadStart)delegate
			{
				WorkerTask();
			});
			thread.Start();
		}

		private void WorkerTask()
		{
			listener = new HttpListener();
			listener.Prefixes.Add($"http://localhost:{port}/");
			listener.Start();
			Plugin.Logger.LogInfo((object)$"Listening on port :{port}");
			while (true)
			{
				HttpListenerContext context = listener.GetContext();
				HttpListenerRequest request = context.Request;
				using HttpListenerResponse httpListenerResponse = context.Response;
				Plugin.Logger.LogInfo((object)("[Webserver] Handling request for " + request.HttpMethod + " : " + request.Url?.ToString() + " : " + request.UserAgent));
				httpListenerResponse.Headers.Set("Access-Control-Allow-Origin", "*");
				httpListenerResponse.Headers.Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
				httpListenerResponse.StatusCode = 200;
				httpListenerResponse.StatusDescription = "status ok";
				string httpMethod = request.HttpMethod;
				string text = httpMethod;
				if (text == "POST")
				{
					string token;
					using (StreamReader streamReader = new StreamReader(request.InputStream, request.ContentEncoding))
					{
						token = streamReader.ReadToEnd();
					}
					Plugin.Logger.LogInfo((object)"Received token from HTTP server, forwarding to uploader...");
					Plugin.uploader.HandleLogin(token);
				}
				else
				{
					httpListenerResponse.Headers.Set("Content-Type", "text/plain");
					string s = "spook.tube Content Warning Mod server";
					byte[] bytes = Encoding.UTF8.GetBytes(s);
					httpListenerResponse.ContentLength64 = bytes.Length;
					using Stream stream = httpListenerResponse.OutputStream;
					stream.Write(bytes, 0, bytes.Length);
				}
				httpListenerResponse.Close();
			}
		}
	}
	[BepInPlugin("spook.tube", "spook.tube", "1.0.0")]
	[ContentWarningPlugin("spook.tube", "spook.tube", true)]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony harmony;

		public static ConfigEntry<string> URL;

		public static Uploader uploader;

		public static string modpackID;

		public static Action onFinishAuth;

		internal static ManualLogSource Logger;

		private void Awake()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			URL = ((BaseUnityPlugin)this).Config.Bind<string>("Networking", "spook.tube Host", "https://spook.tube", "A PeerTube-compatible instance");
			LoadModpackID();
			Logger.LogInfo((object)"spook.tube has loaded.");
			if (modpackID != null)
			{
				Logger.LogWarning((object)("Using modpack ID: " + modpackID));
			}
			harmony = new Harmony("tube.spook.mod");
			harmony.PatchAll();
			uploader = new PTUploader(URL.Value);
			HTTPServer hTTPServer = new HTTPServer(48923);
			hTTPServer.Start();
		}

		private void OnDestroy()
		{
			harmony.UnpatchSelf();
			Logger.LogInfo((object)"spook.tube has unloaded.");
		}

		public static string getVideoMime(string path)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			dictionary.Add(".webm", "video/webm");
			dictionary.Add(".mp4", "video/mp4");
			string extension = Path.GetExtension(path);
			if (dictionary.ContainsKey(extension))
			{
				return DictionaryExtensions.Get<string, string>((IDictionary<string, string>)dictionary, extension, (string)null);
			}
			return null;
		}

		public void LoadModpackID()
		{
			List<string> list = Directory.GetDirectories(Paths.PluginPath, "SpookTube", SearchOption.AllDirectories).ToList();
			foreach (string item in list)
			{
				string path = Path.Combine(item, "modpackid.txt");
				if (File.Exists(path))
				{
					modpackID = File.ReadAllText(path);
				}
			}
		}

		public static List<LoadedPlugin> GetLoadedPlugins()
		{
			List<LoadedPlugin> list = new List<LoadedPlugin>();
			foreach (ThunderstoreMod loadedMod in Library.GetLoadedMods())
			{
				list.Add(new LoadedPlugin
				{
					ThunderstoreName = loadedMod.name,
					Version = $"{loadedMod.versionNumber.major}.{loadedMod.versionNumber.minor}.{loadedMod.versionNumber.patch}"
				});
			}
			return list;
		}

		public static List<ContentPlayer> GetPlayers()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			List<ContentPlayer> list = new List<ContentPlayer>();
			PlayerVisor[] array = Object.FindObjectsOfType<PlayerVisor>();
			PlayerVisor[] array2 = array;
			foreach (PlayerVisor val in array2)
			{
				PhotonView component = ((Component)val).gameObject.GetComponent<PhotonView>();
				ContentPlayer item = new ContentPlayer
				{
					FaceText = ((TMP_Text)val.visorFaceText).text,
					FaceColor = ColorUtility.ToHtmlStringRGB(val.visorColor.Value),
					FaceRotation = (int)val.FaceRotation
				};
				list.Add(item);
			}
			return list;
		}

		public static void CreateUploadButton()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			if (!GameObject.Find("ShowVideoState/VIDEO/VideoDone").DoesObjectExist("STUpload"))
			{
				GameObject val = GameObject.Find("VideoDone/Replay");
				GameObject val2 = Object.Instantiate<GameObject>(val);
				((Object)val2).name = "STUpload";
				val2.transform.SetParent(val.transform.parent, true);
				val2.transform.localPosition = new Vector3(0f, -125f, 0f);
				val2.transform.rotation = val.transform.rotation;
				val2.transform.localScale = new Vector3(1f, 1f, 1f);
				GameObject val3 = GameObject.Find("STUpload/Image");
				val3.transform.localPosition = new Vector3(-100f, 0f, 0f);
				val3.transform.rotation = val.transform.rotation;
				val3.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
				Sprite sprite = GameObject.Find("VideoDone/SaveVideo/Image").GetComponent<Image>().sprite;
				val3.GetComponent<Image>().sprite = sprite;
				GameObject val4 = GameObject.Find("STUpload/Text (TMP)");
				val4.transform.localPosition = new Vector3(0f, 0f, 0f);
				val4.transform.rotation = val.transform.rotation;
				val4.transform.localScale = new Vector3(1f, 1f, 1f);
				((Behaviour)val4.GetComponent<GameObjectLocalizer>()).enabled = false;
				TextMeshProUGUI component = val4.GetComponent<TextMeshProUGUI>();
				((TMP_Text)component).SetText("UPLOAD TO SPOOK.TUBE", true);
				GameObject val5 = GameObject.Find("UploadMachine2").FindObject("ReplayInt");
				GameObject val6 = Object.Instantiate<GameObject>(val5);
				((Object)val6).name = "STUploadInt";
				Object.Destroy((Object)(object)val6.GetComponent<ReplayVideoInteractable>());
				val6.AddComponent<UploadButton>();
				val6.transform.SetParent(val5.transform.parent, true);
				val6.transform.position = val2.transform.position;
				val6.transform.rotation = val2.transform.rotation;
				val6.transform.localScale = new Vector3(1f, 1f, 1f);
			}
		}

		public static void StartUpload()
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = GameObject.Find("McScreen/Content").FindObject("ShowVideoState");
			if ((Object)(object)val == (Object)null)
			{
				Logger.LogError((object)"Failed to get the ShowVideoState object");
				return;
			}
			UploadCompleteUI component = val.GetComponent<UploadCompleteUI>();
			if ((Object)(object)component == (Object)null)
			{
				Logger.LogError((object)"Failed to get UploadCompleteUI");
				return;
			}
			object value = new Traverse((object)component).Field("m_replayVideo").GetValue();
			IPlayableVideo video = (IPlayableVideo)((value is IPlayableVideo) ? value : null);
			GameObject parent = GameObject.Find("UploadMachine2/McScreen").FindObject("Content");
			GameObject val2 = parent.FindObject("ShowVideoState");
			GameObject UploadingState = parent.FindObject("UploadingState");
			UploadingState.SetActive(true);
			val2.SetActive(false);
			VideoMeta meta = new VideoMeta
			{
				Plugins = GetLoadedPlugins(),
				Players = GetPlayers()
			};
			uploader.Upload(video, meta).ContinueWith(delegate
			{
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				UploadingState.SetActive(false);
				object value2 = new Traverse((object)GameObject.Find("/Tools/UploadMachine2").GetComponent<UploadVideoStation>()).Field("m_stateMachine").GetValue();
				UploadVideoStationStateMachine val3 = (UploadVideoStationStateMachine)((value2 is UploadVideoStationStateMachine) ? value2 : null);
				((StateMachine<UploadVideoStationState>)(object)val3).SwitchState<UploadVideoState>();
			});
		}

		public static void WaitForLogin(Action onFinish)
		{
			onFinishAuth = onFinish;
			uploader.OpenAuth();
		}
	}
	public class LoadedPlugin
	{
		public string ThunderstoreName;

		public string Version;
	}
	public class ContentPlayer
	{
		public string SteamID;

		public string FaceText;

		public string FaceColor;

		public int FaceRotation;
	}
	internal class UploadButton : Interactable
	{
		public UploadVideoStation UploadVideoStation;

		private void Start()
		{
			base.hoverText = "Upload to spook.tube";
		}

		public override void Interact(Player player)
		{
			Plugin.Logger.LogWarning((object)"Upload to spook.tube button has been pressed");
			if (Plugin.uploader.IsAuthenticated())
			{
				Plugin.StartUpload();
			}
			else
			{
				Plugin.uploader.OpenAuth();
			}
		}

		public override void OnStartHover(Player player)
		{
		}

		public override void OnEndHover(Player player)
		{
		}
	}
	internal static class Utils
	{
		public static GameObject FindObject(this GameObject parent, string name)
		{
			Transform[] componentsInChildren = parent.GetComponentsInChildren<Transform>(true);
			Transform[] array = componentsInChildren;
			foreach (Transform val in array)
			{
				if (((Object)val).name.StartsWith(name))
				{
					return ((Component)val).gameObject;
				}
			}
			return null;
		}

		public static bool DoesObjectExist(this GameObject parent, string name)
		{
			return (Object)(object)parent.FindObject(name) != (Object)null;
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "spook.tube";

		public const string PLUGIN_NAME = "spook.tube";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace spook.tube.Uploaders
{
	public class PTUploader : Uploader
	{
		private class UploadResult_Success
		{
			public class Video
			{
				public int id;

				public string shortUUID;

				public string uuid;
			}

			public Video video;
		}

		private class PTAccount
		{
			public string username;

			public PTChannel[] videoChannels;
		}

		private class ChannelResponse
		{
			public int total;

			public PTChannel[] data;
		}

		private class PTChannel
		{
			public int id;

			public string displayName;
		}

		private string hostname;

		private static string token;

		private HttpClient client;

		private static int channelId;

		public PTUploader(string hostname)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			this.hostname = hostname;
			client = new HttpClient();
		}

		public override bool IsAuthenticated()
		{
			return token != null;
		}

		private HttpRequestMessage buildRequest(string endpoint, HttpMethod method)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			HttpRequestMessage val = new HttpRequestMessage
			{
				RequestUri = new Uri(hostname + endpoint),
				Method = method
			};
			((HttpHeaders)val.Headers).Add("Authorization", "Bearer " + token);
			return val;
		}

		private string getVideoURL(string shortUUID)
		{
			return hostname + "/videos/update/" + shortUUID + "?ref=mod-upload";
		}

		public override void OpenAuth()
		{
			Application.OpenURL(hostname + "/p/mod/token");
		}

		public override void HandleLogin(string token)
		{
			PTUploader.token = token;
			HttpResponseMessage result = client.SendAsync(buildRequest("/api/v1/users/me", HttpMethod.Get)).Result;
			string result2 = result.Content.ReadAsStringAsync().Result;
			Plugin.Logger.LogInfo((object)("Token check result: " + result2));
			if (result.StatusCode == HttpStatusCode.OK)
			{
				PTAccount pTAccount = JsonConvert.DeserializeObject<PTAccount>(result2);
				Plugin.Logger.LogInfo((object)("Logged in as " + pTAccount.username));
				if (pTAccount.videoChannels.Length == 0)
				{
					Plugin.Logger.LogError((object)"User has no channels? Cannot load.");
					return;
				}
				PTChannel pTChannel = pTAccount.videoChannels[0];
				channelId = pTChannel.id;
				Plugin.Logger.LogInfo((object)$"Using channel {pTChannel.displayName} ({pTChannel.id})");
				Plugin.onFinishAuth?.Invoke();
			}
			else
			{
				Plugin.Logger.LogWarning((object)"Failed to login");
			}
		}

		public override Task Upload(IPlayableVideo recording, VideoMeta meta)
		{
			string path = default(string);
			recording.TryGetVideoPath(ref path);
			return Task.Factory.StartNew(delegate
			{
				UploadThread(path, meta);
			});
		}

		private void UploadThread(string path, VideoMeta meta)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Expected O, but got Unknown
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			Plugin.Logger.LogInfo((object)("Starting upload of " + path));
			Plugin.Logger.LogInfo((object)("File name: " + Path.GetFileName(path)));
			string videoMime = Plugin.getVideoMime(path);
			Plugin.Logger.LogInfo((object)("File mime type: " + videoMime));
			string text = "In-Game Upload";
			if (videoMime == null)
			{
				Plugin.Logger.LogWarning((object)("I don't know what mime type the video is (" + Path.GetExtension(path) + ")"));
				return;
			}
			try
			{
				MultipartFormDataContent val = new MultipartFormDataContent();
				val.Add((HttpContent)new StringContent(channelId.ToString() ?? ""), "channelId");
				val.Add((HttpContent)new StringContent(text), "name");
				val.Add((HttpContent)new StringContent("true"), "usingMod");
				if (Plugin.modpackID != null)
				{
					Plugin.Logger.LogWarning((object)("Uploading video using modpackId " + Plugin.modpackID));
					val.Add((HttpContent)new StringContent(Plugin.modpackID), "modpackId");
				}
				val.Add((HttpContent)new StringContent(JsonConvert.SerializeObject((object)meta.Plugins)), "mods");
				val.Add((HttpContent)new StringContent(JsonConvert.SerializeObject((object)meta.Players)), "players");
				ByteArrayContent val2 = new ByteArrayContent(File.ReadAllBytes(path));
				((HttpContent)val2).Headers.ContentType = MediaTypeHeaderValue.Parse(videoMime);
				val.Add((HttpContent)(object)val2, "videofile", Path.GetFileName(path));
				HttpRequestMessage val3 = buildRequest("/api/v1/videos/upload", HttpMethod.Post);
				val3.Content = (HttpContent)(object)val;
				HttpResponseMessage result = client.SendAsync(val3).Result;
				string result2 = result.Content.ReadAsStringAsync().Result;
				Plugin.Logger.LogInfo((object)("Upload response: " + result2));
				HttpStatusCode statusCode = result.StatusCode;
				HttpStatusCode httpStatusCode = statusCode;
				if (httpStatusCode == HttpStatusCode.OK)
				{
					UploadResult_Success uploadResult_Success = JsonConvert.DeserializeObject<UploadResult_Success>(result2);
					Plugin.Logger.LogInfo((object)("Successfully uploaded " + uploadResult_Success.video.shortUUID));
					Application.OpenURL(getVideoURL(uploadResult_Success.video.shortUUID));
				}
				else
				{
					Plugin.Logger.LogError((object)("Error while uploading " + result.StatusCode.ToString() + " : " + result2));
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}
	}
	public abstract class Uploader
	{
		public abstract bool IsAuthenticated();

		public abstract void OpenAuth();

		public abstract void HandleLogin(string token);

		public abstract Task Upload(IPlayableVideo video, VideoMeta meta);
	}
	public class VideoMeta
	{
		public List<LoadedPlugin> Plugins;

		public List<ContentPlayer> Players;
	}
}