private static async void DoCommand(string commandDescription, Func <Task <string> > request, Action <JSONNode> handleSuccess, Action <string> onError) { OnCommandStart?.Invoke(); TryLogVerbose(commandDescription); if (CheckConnectionKey(commandDescription, onError)) { OnCommandEnd?.Invoke(); return; } var response = await request(); var responseJson = JSONNode.Parse(response); TryLogResponse(commandDescription, responseJson); if (ReportErrors(commandDescription, responseJson, onError)) { OnCommandEnd?.Invoke(); return; } try { handleSuccess(responseJson); OnCommandEnd?.Invoke(); } catch (Exception e) { TryLogError(commandDescription, e.Message, onError); OnCommandEnd?.Invoke(); } }
//============================================================================================================// // // // SYNC // // // //============================================================================================================// /// <summary> /// Runs a series of checks to determine the server time sync offset value /// This is important if you want the Handy's movements to stay in-sync with locally-playing video content! /// Once this function completes, the value of ServerTimeOffset is automatically updated, and will be applied whenever relevant /// Be warned! You only have 120 API calls per hour per device and each trip taken during this function counts as one of them! /// </summary> /// <param name="trips">How many requests to make. The offset value is the average offset time for each trip. Accuracy improves with more trips, the API recommends 30</param> /// <param name="onSuccess">Callback indicating success, contains the newly calculated server time offset</param> /// <param name="onError">Callback indicating failure, contains the error message</param> public static async void GetServerTime(int trips = 30, Action <long> onSuccess = null, Action <string> onError = null) { const string commandDescription = "Get Server Time"; OnCommandStart?.Invoke(); TryLogVerbose(commandDescription); if (CheckConnectionKey(commandDescription, onError)) { OnCommandEnd?.Invoke(); return; } var offsetAggregate = 0L; for (var i = 0; i < trips; i++) { var startTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); var response = await GetAsync(GetUrl("getServerTime")); var responseJson = (JSONObject)JSONNode.Parse(response); TryLogResponse(commandDescription, responseJson); if (responseJson["error"] != null) { TryLogError(commandDescription, responseJson["error"], onError); OnCommandEnd?.Invoke(); return; } var endTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); var rtd = endTime - startTime; var estimatedServerTime = long.Parse(responseJson["serverTime"]) + rtd / 2; var offset = estimatedServerTime - endTime; offsetAggregate += offset; } offsetAggregate /= trips; ServerTimeOffset = offsetAggregate; if ((int)LogMode >= (int)HandyLogMode.Verbose) { Debug.Log($"<color=blue>Calculated server offset as {ServerTimeOffset} milliseconds</color>"); } onSuccess?.Invoke(ServerTimeOffset); OnCommandEnd?.Invoke(); }
/// <summary> /// Converts a list of time/position pairs to a CSV file hosted on Handy's servers, ready to be loaded onto a Handy using PrepareSync /// </summary> /// <param name="patternData">The data to be converted. /// The x coordinate represents the time in milliseconds from the beginning of the file /// The y coordinate represents the position as a percentage of the current stroke value that the Handy should be at, at the given time value</param> /// <param name="onSuccess">Callback indicating success, contains the URL of the newly created CSV file</param> /// <param name="onError">Callback indicating failure, contains the error message</param> public static async void PatternToUrl(Vector2Int[] patternData, Action <string> onSuccess = null, Action <string> onError = null) { const string commandDescription = "Pattern to URL"; OnCommandStart?.Invoke(); TryLogVerbose(commandDescription); if (patternData == null || patternData.Length == 0) { TryLogError(commandDescription, "No pattern data provided", onError); OnCommandEnd?.Invoke(); return; } try { var csv = "#{\"type\":\"handy\"}"; foreach (var item in patternData) { csv += $"\n{item.x},{item.y}"; } var bytes = Encoding.ASCII.GetBytes(csv); var response = await PostAsync( "https://www.handyfeeling.com/api/sync/upload", $"UnityGenerated_{DateTimeOffset.Now.ToUnixTimeMilliseconds()}.csv", new MemoryStream(bytes) ); var responseJson = JSONNode.Parse(response); TryLogResponse(commandDescription, responseJson); var url = responseJson["url"] == null ? "" : (string)responseJson["url"]; onSuccess?.Invoke(url); OnCommandEnd?.Invoke(); } catch (Exception e) { TryLogError(commandDescription, "Unexpected error: " + e.Message, onError); OnCommandEnd?.Invoke(); } }
/// <summary> /// Uploads a CSV file to Handy's servers, ready to be loaded onto a Handy using PrepareSync /// </summary> /// <param name="csv">The CSV to be loaded, as a string. Each line should be in the format [time (ms)],[position (%)]</param> /// <param name="fileName">Optional filename, one will be generated if left off</param> /// <param name="onSuccess">Callback indicating success, contains the URL of the newly uploaded CSV file</param> /// <param name="onError">Callback indicating failure, contains the error message</param> public static async void CsvToUrl(string csv, string fileName = "", Action <string> onSuccess = null, Action <string> onError = null) { const string commandDescription = "CSV to URL"; OnCommandStart?.Invoke(); TryLogVerbose(commandDescription); if (string.IsNullOrEmpty(csv)) { TryLogError(commandDescription, "No CSV provided", onError); OnCommandEnd?.Invoke(); return; } try { var bytes = Encoding.ASCII.GetBytes(csv); var response = await PostAsync( "https://www.handyfeeling.com/api/sync/upload", string.IsNullOrEmpty(fileName) ?$"UnityGenerated_{DateTimeOffset.Now.ToUnixTimeMilliseconds()}.csv" : fileName, new MemoryStream(bytes) ); var responseJson = JSONNode.Parse(response); TryLogResponse(commandDescription, responseJson); var url = responseJson["url"] == null ? "" : (string)responseJson["url"]; onSuccess?.Invoke(url); OnCommandEnd?.Invoke(); } catch (Exception e) { TryLogError(commandDescription, "Unexpected error: " + e.Message, onError); OnCommandEnd?.Invoke(); } }
private void HandleCommand(string[] args) { string[] sendArgs = new string[0]; if (args.Length > 1) { sendArgs = new string[args.Length - 1]; for (int i = 1; i < args.Length; i++) { sendArgs[i - 1] = args[i]; } } string name = args[0].ToLower().Trim(); CommandEventArgs eargs = new CommandEventArgs(name, sendArgs); bool canceled = OnPlayerCommand.Call(this, eargs, OnAllPlayersCommand).Canceled; if (canceled) // If any event canceled us { return; } if (Block.NameToBlock(name) != Block.BlockList.UNKNOWN) { sendArgs = new string[] { name }; name = "mode"; } if (Command.Commands.ContainsKey(name)) { ThreadPool.QueueUserWorkItem(delegate { ICommand cmd = Command.Commands[name]; if (ServerSettings.GetSettingBoolean("AgreeingToRules")) { if (!Server.AgreedPlayers.Contains(Username) && Group.Permission < 80 && name != "rules" && name != "agree" && name != "disagree") { SendMessage("You need to /agree to the /rules before you can use commands!"); return; } } if (!Group.CanExecute(cmd)) { SendMessage(Colors.red + "You cannot use /" + name + "!"); return; } try { cmd.Use(this, sendArgs); OnCommandEnd.Call(this, new CommandEndEventArgs(cmd, sendArgs), OnAllCommandEnd); } catch (Exception ex) { Logger.Log("[Error] An error occured when " + Username + " tried to use " + name + "!", System.Drawing.Color.Red, System.Drawing.Color.Black); Logger.LogError(ex); } if (ExtraData.ContainsKey("LastCmd")) { if ((string)(ExtraData["LastCmd"]) != String.Join(" ", args)) { ExtraData["LastCmd"] = String.Join(" ", args); } } else { ExtraData.Add("LastCmd", name); } }); } else { SendMessage("Unknown command \"" + name + "\"!"); } }