inohara/Inohara/Exporter.cs

283 lines
9.7 KiB
C#
Raw Normal View History

2024-05-19 22:24:05 +00:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MU3.CustomUI;
using MU3.Game;
using MU3.User;
using MU3.Util;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
namespace Inohara;
public class Exporter: SingletonMonoBehaviour<Exporter> {
private struct Config {
public bool Enable;
public int Timeout;
public string BaseUrl;
public string StatusPoint;
public string ImportPoint;
public bool EnableText;
2024-05-20 17:25:56 +00:00
public bool ExportPBs;
2024-05-19 22:24:05 +00:00
}
static readonly float REQ2_DELAY = 4f;
static readonly float DRAW_DURATION = 4f;
protected new bool _dontDestroyOnLoad = true;
private Config _cfg;
private readonly Dictionary<string, string> _tokens = new();
private string _currToken = "";
private readonly Font _arial = Resources.GetBuiltinResource<Font>("Arial.ttf");
private List<BatchScore> _scores = new();
2024-05-20 17:25:56 +00:00
private void Log(object o, bool drawText) {
2024-05-19 22:24:05 +00:00
Debug.Log("[Inohara] " + o.ToString());
2024-05-20 17:25:56 +00:00
if(_cfg.EnableText && drawText) {
2024-05-19 22:24:05 +00:00
StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 1f, 1f, 1.0f)));
}
}
private void LogSuccess(object o) {
Debug.Log("[Inohara] " + o.ToString());
if(_cfg.EnableText) {
StartCoroutine(DrawMessage(o.ToString(), new Color(0f, 0.83f, 0.14f, 1.0f)));
}
}
private void LogError(object o) {
Debug.LogError("[Inohara] " + o.ToString());
if(_cfg.EnableText) {
StartCoroutine(DrawMessage(o.ToString(), new Color(1f, 0.42f, 0.42f, 1.0f)));
}
}
public void LoadCfg() {
2024-05-20 17:25:56 +00:00
_cfg.Enable = false;
2024-05-19 22:24:05 +00:00
try {
using StreamReader reader = new(Path.Combine(Application.dataPath, "../inohara.cfg"));
Dictionary<string, string> options = new();
Dictionary<string, string> dict = null;
string line;
while ((line = reader.ReadLine()) != null) {
line = line.Split(new char[] { '#' }, 2)[0];
if(line.ToLower() == "[keys]") {
dict = _tokens;
} else if(line.ToLower() == "[options]") {
dict = options;
} else if(line.StartsWith("[")) {
dict = null;
} else if(dict != null) {
string[] tokens = line.Split(new char[] { '=' }, 2);
if(tokens.Length == 2) {
dict.Add(tokens[0].Trim().ToLower(), tokens[1].Trim());
}
}
}
if(!options.ContainsKey("enable") || !bool.TryParse(options["enable"], out _cfg.Enable)) {
_cfg.Enable = false;
}
if(!options.ContainsKey("enableosd") || !bool.TryParse(options["enableosd"], out _cfg.EnableText)) {
_cfg.EnableText = false;
}
2024-05-20 17:25:56 +00:00
if(!options.ContainsKey("exportpbs") || !bool.TryParse(options["exportpbs"], out _cfg.ExportPBs)) {
_cfg.ExportPBs = false;
}
2024-05-19 22:24:05 +00:00
if(!options.ContainsKey("timeout") || !int.TryParse(options["timeout"], out _cfg.Timeout)) {
_cfg.Timeout = 3;
}
if(!options.TryGetValue("baseurl", out _cfg.BaseUrl)) {
LogError("Config error: missing option BaseUrl");
_cfg.Enable = false;
}
if(!options.TryGetValue("status", out _cfg.StatusPoint)) {
_cfg.StatusPoint = "/api/v1/status";
}
if(!options.TryGetValue("import", out _cfg.ImportPoint)) {
_cfg.ImportPoint = "/ir/direct-manual/import";
}
} catch (IOException e) {
LogError("Config loading failed: " + e);
_cfg.Enable = false;
}
if(_cfg.Enable) {
Log("Score submissions enabled", true);
} else {
Log("Score submissions disabled", true);
}
}
public void Authorize() {
if(!_cfg.Enable) {
return;
}
var username = Singleton<UserManager>.instance.UserName
.Normalize(NormalizationForm.FormKC)
.ToLower();
_currToken = "";
_scores.Clear();
string tmpBearer;
if(!_tokens.ContainsKey(username) && !_tokens.ContainsKey("*")) {
LogError("User " + username + " not found");
return;
} else {
if(!_tokens.TryGetValue(username, out tmpBearer)) {
tmpBearer = _tokens["*"];
}
}
StartCoroutine(AuthorizeReq(tmpBearer));
}
private IEnumerator AuthorizeReq(string tmpBearer) {
using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.StatusPoint, "GET");
req.SetRequestHeader("Authorization", "Bearer " + tmpBearer);
req.downloadHandler = new DownloadHandlerBuffer();
2024-05-20 17:25:56 +00:00
req.timeout = _cfg.Timeout;
2024-05-19 22:24:05 +00:00
yield return req.Send();
try {
if(req.responseCode == 200) {
var res = JsonUtility.FromJson<StatusResponse>(req.downloadHandler.text);
if(res.success == true) {
if(res.body.permissions.Contains("submit_score")) {
LogSuccess("Logged in");
Log(string.Format("Tachi {0} (user id={1})", res.body.version, res.body.whoami), false);
_currToken = tmpBearer;
} else {
LogError("Missing the submit_score permission");
}
} else {
LogError("Unable to log into Tachi");
}
}
} catch(Exception e) {
LogError("Unable to log into Tachi: " + e);
}
}
public void Export(BattleResult result, SessionInfo info) {
if(_currToken == "" || !_cfg.Enable) {
return;
}
StartCoroutine(ExportReq(result, info));
}
private IEnumerator ExportReq(BattleResult result, SessionInfo info) {
using var req = new UnityWebRequest(_cfg.BaseUrl + _cfg.ImportPoint, "POST");
req.timeout = _cfg.Timeout;
req.SetRequestHeader("Authorization", "Bearer " + _currToken);
req.SetRequestHeader("Content-Type", "application/json");
_scores.Add(Util.CreateScore(result, info));
2024-05-20 17:25:56 +00:00
var batch = Util.CreateBatch(_scores, "inohara");
2024-05-19 22:24:05 +00:00
byte[] jsonToSend = new UTF8Encoding().GetBytes(batch);
req.uploadHandler = new UploadHandlerRaw(jsonToSend);
req.downloadHandler = new DownloadHandlerBuffer();
Log("Uploading " + _scores.Count + " score(s)", false);
yield return req.Send();
if(req.responseCode == 200) {
LogSuccess("Upload successful");
Log("Warning: Non-deferred DIRECT-MANUAL is untested", false);
_scores.Clear();
} else if (req.responseCode == 202) {
var res = JsonUtility.FromJson<BatchResponse>(req.downloadHandler.text);
if(res.success == true) {
StartCoroutine(ExportReq2(res.body.url));
} else {
LogError("Upload failed: " + res.body);
}
} else {
LogError(string.Format("Upload failed ({0}): {1}", req.responseCode, req.error));
}
}
private IEnumerator ExportReq2(string url) {
yield return new WaitForSeconds(REQ2_DELAY);
using var req = new UnityWebRequest(url, "GET");
req.downloadHandler = new DownloadHandlerBuffer();
yield return req.Send();
if(req.responseCode == 200) {
var res = JsonUtility.FromJson<BatchResponse2>(req.downloadHandler.text);
if(res.success == true) {
LogSuccess("Upload successful");
_scores.Clear();
} else {
LogError("Upload failed");
}
} else {
LogError(string.Format("Upload failed ({0}): {1}", req.responseCode, req.error));
}
}
2024-05-20 17:25:56 +00:00
public void ExportPBs() {
if(!_cfg.ExportPBs) {
return;
}
var userMusic = Singleton<UserManager>.instance.userMusic;
var scores = new List<BatchScore>();
foreach(var x in userMusic.Values) {
foreach(var y in x.UserFumen) {
if(y != null) {
scores.Add(Util.CreatePB(y));
}
}
}
var batch = Util.CreateBatch(scores, "inohara-pb");
using StreamWriter writer = new(Path.Combine(Application.dataPath, "../batch-manual.json"));
writer.Write(batch);
Log("Exported PBs to batch-manual.json", false);
}
2024-05-19 22:24:05 +00:00
// This is just for fun
private IEnumerator DrawMessage(string message, Color color) {
GameObject canvasGO = new() {
name = "Canvas"
};
DontDestroyOnLoad(canvasGO);
canvasGO.AddComponent<Canvas>();
canvasGO.AddComponent<CanvasScaler>();
canvasGO.AddComponent<GraphicRaycaster>();
Canvas canvas = canvasGO.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
GameObject textGO = new();
textGO.transform.parent = canvasGO.transform;
textGO.AddComponent<MU3Text>();
var text = textGO.GetComponent<MU3Text>();
text.font = _arial;
text.text = message;
text.fontSize = 20;
text.color = color;
text.alignment = TextAnchor.UpperCenter;
RectTransform rectTransform = text.GetComponent<RectTransform>();
rectTransform.localPosition = new Vector3(0, 0, 0);
rectTransform.sizeDelta = new Vector2(1080, 1400);
yield return new WaitForSeconds(DRAW_DURATION);
Destroy(canvasGO);
Destroy(text);
}
}