From e3bb28ff8ed67686d0110153de18c4baf364c3e8 Mon Sep 17 00:00:00 2001 From: Madpeterz <57852827+Madpeterz@users.noreply.github.com> Date: Sun, 31 Jan 2021 19:23:20 +0000 Subject: [PATCH] HTTP and Discord updates config values no longer used: Http_Port Http_PublicUrl new config values: DiscordFull_Keep_GroupChat --- BSBshared/Json/JsonConfig.cs | 2 - BSBshared/Json/JsonConfigApi.cs | 25 +- CLI/BetterSecondBot.csproj | 21 +- CLI/CliExitOnLogout.cs | 9 +- CLI/DiscordSupervisor/Status.cs | 2 +- CLI/DiscordSupervisor/relay.cs | 2 +- CLI/HttpServer/SecondBotHttpServer.cs | 277 ----------- CLI/HttpServer/control/Actions/logout.cs | 32 -- CLI/HttpServer/control/Interface/cmd_lib.cs | 42 -- CLI/HttpServer/http_commands.cs | 77 --- CLI/HttpService/HttpAsService.cs | 164 ++++++ CLI/HttpService/Inventory.cs | 44 ++ CLI/HttpService/Secondbot.cs | 107 ++++ CLI/HttpWebUi/theme/css/site.css | 4 - CLI/HttpWebUi/theme/images/favicon.png | Bin 4572 -> 0 bytes CLI/HttpWebUi/theme/images/logo.png | Bin 28566 -> 0 bytes CLI/HttpWebUi/theme/js/site.js | 201 -------- CLI/HttpWebUi/theme/layout/cdn.css.layout | 3 - CLI/HttpWebUi/theme/layout/cdn.js.layout | 5 - CLI/HttpWebUi/theme/layout/full.layout | 35 -- CLI/HttpWebUi/theme/layout/login.block | 14 - CLI/HttpWebUi/theme/layout/sidemenu.layout | 61 --- CLI/HttpWebUi/webui.cs | 514 ------------------- CLI/WikiMake/wiki_header.txt | 54 +- CLI/WikiMake/wikimake.cs | 520 +++++++++++++++++++- Core/BSB.csproj | 4 +- 26 files changed, 856 insertions(+), 1363 deletions(-) delete mode 100644 CLI/HttpServer/SecondBotHttpServer.cs delete mode 100644 CLI/HttpServer/control/Actions/logout.cs delete mode 100644 CLI/HttpServer/control/Interface/cmd_lib.cs delete mode 100644 CLI/HttpServer/http_commands.cs create mode 100644 CLI/HttpService/HttpAsService.cs create mode 100644 CLI/HttpService/Inventory.cs create mode 100644 CLI/HttpService/Secondbot.cs delete mode 100644 CLI/HttpWebUi/theme/css/site.css delete mode 100644 CLI/HttpWebUi/theme/images/favicon.png delete mode 100644 CLI/HttpWebUi/theme/images/logo.png delete mode 100644 CLI/HttpWebUi/theme/js/site.js delete mode 100644 CLI/HttpWebUi/theme/layout/cdn.css.layout delete mode 100644 CLI/HttpWebUi/theme/layout/cdn.js.layout delete mode 100644 CLI/HttpWebUi/theme/layout/full.layout delete mode 100644 CLI/HttpWebUi/theme/layout/login.block delete mode 100644 CLI/HttpWebUi/theme/layout/sidemenu.layout delete mode 100644 CLI/HttpWebUi/webui.cs diff --git a/BSBshared/Json/JsonConfig.cs b/BSBshared/Json/JsonConfig.cs index 4855607..88c1183 100644 --- a/BSBshared/Json/JsonConfig.cs +++ b/BSBshared/Json/JsonConfig.cs @@ -44,9 +44,7 @@ namespace BetterSecondBotShared.Json // HTTP interface public bool Http_Enable { get; set; } - public int Http_Port { get; set; } public string Http_Host { get; set; } - public string Http_PublicUrl { get; set; } // Name2Key DB public bool Name2Key_Enable { get; set; } diff --git a/BSBshared/Json/JsonConfigApi.cs b/BSBshared/Json/JsonConfigApi.cs index fb237b3..5722a20 100644 --- a/BSBshared/Json/JsonConfigApi.cs +++ b/BSBshared/Json/JsonConfigApi.cs @@ -126,17 +126,9 @@ namespace BetterSecondBotShared.Json { return "Allow the bot to start a HTTP interface (Required for web UI and Post commands)"; } - else if (cmd == "Http_Port") - { - return "The port the HTTP interface should be running on"; - } else if (cmd == "Http_Host") { - return "The URL the interface is on example use \"docker\" for auto or \"http://localhost:portnum\""; - } - else if (cmd == "Http_PublicUrl") - { - return "The public url used to access the web ui"; + return "The URL the interface is on example use \"docker\" for auto or \"http://*:8080\" to listen on port 8080
Note: for windows you might need to accept a firewall alert and restart the app!"; } return ""; } @@ -265,15 +257,14 @@ namespace BetterSecondBotShared.Json else if (cmd == "DiscordFull_Enable") { return "False"; } else if (cmd == "DiscordFull_Token") { return ""; } else if (cmd == "DiscordFull_ServerID") { return ""; } + else if (cmd == "DiscordFull_Keep_GroupChat") { return "False"; } return ""; } protected string GetValueHint_HTTP(string cmd) { // HTTP if (cmd == "Http_Enable") { return "False"; } - else if (cmd == "Http_Port") { return "80"; } - else if (cmd == "Http_Host") { return "http://localhost:80"; } - else if (cmd == "Http_PublicUrl") { return "http://localhost/"; } + else if (cmd == "Http_Host") { return "http://*:8080"; } return ""; } protected string GetValueHint_DiscordTTS(string cmd) @@ -409,13 +400,13 @@ namespace BetterSecondBotShared.Json { LogFormater.Warn("Http disabled: Security_WebUIKey length must be 12 or more"); } - else if (jsonConfig.Http_Port < 80) - { - LogFormater.Warn("Http disabled: Httpport range below protected range - Given: (" + jsonConfig.Http_Port + ")"); - } else if ((jsonConfig.Http_Host != "docker") && (jsonConfig.Http_Host.StartsWith("http") == false)) { - LogFormater.Warn("Http disabled: Http_Host must be vaild: http://XXXXXX or \"docker\" - Given: (" + jsonConfig.Http_Host + ")"); + LogFormater.Warn("Http disabled: Http_Host must be vaild or \"docker\" - Given: (" + jsonConfig.Http_Host + ")"); + } + else if (jsonConfig.Http_Host == "docker") + { + jsonConfig.Http_Host = "http://*:8080"; } else { diff --git a/CLI/BetterSecondBot.csproj b/CLI/BetterSecondBot.csproj index a514855..ebb6bf7 100644 --- a/CLI/BetterSecondBot.csproj +++ b/CLI/BetterSecondBot.csproj @@ -26,35 +26,18 @@ linux-x64 - - - - - - - - - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - diff --git a/CLI/CliExitOnLogout.cs b/CLI/CliExitOnLogout.cs index 40fdc58..f55dbb2 100644 --- a/CLI/CliExitOnLogout.cs +++ b/CLI/CliExitOnLogout.cs @@ -1,12 +1,8 @@ -using BetterSecondBot.HttpServer; -using BetterSecondBotShared.IO; +using BetterSecondBot.HttpService; using BetterSecondBotShared.Json; using BetterSecondBotShared.logs; using BetterSecondBotShared.Static; using BSB.bottypes; -using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using static BetterSecondBot.Program; @@ -51,8 +47,11 @@ namespace BetterSecondBot Bot.Start(); if (Config.Http_Enable == true) { + /* SecondBotHttpServer my_http_server = new SecondBotHttpServer(); my_http_server.StartHttpServer(Bot, Config); + */ + new HttpAsService(Bot, Config); } if(use_self_keep_alive == true) { diff --git a/CLI/DiscordSupervisor/Status.cs b/CLI/DiscordSupervisor/Status.cs index 9c1ce6e..ea8cc00 100644 --- a/CLI/DiscordSupervisor/Status.cs +++ b/CLI/DiscordSupervisor/Status.cs @@ -16,7 +16,7 @@ namespace BetterSecondBot.DiscordSupervisor } } } - string DiscordBotStatus_laststatus = ""; + public string GetStatus() { string reply = ""; diff --git a/CLI/DiscordSupervisor/relay.cs b/CLI/DiscordSupervisor/relay.cs index df341ff..a3762a4 100644 --- a/CLI/DiscordSupervisor/relay.cs +++ b/CLI/DiscordSupervisor/relay.cs @@ -106,7 +106,7 @@ namespace BetterSecondBot.DiscordSupervisor else { controler.Bot.Debug("DiscordBotChatControler - Localchat:" + sender_name + ":" + message); - await SendMessageToChannelAsync("localchat", "" + sender_name + ":" + message, "bot", UUID.Zero, "bot").ConfigureAwait(false); + await SendMessageToChannelAsync("localchat", "" + sender_name + ": " + message, "bot", UUID.Zero, "bot").ConfigureAwait(false); } } else diff --git a/CLI/HttpServer/SecondBotHttpServer.cs b/CLI/HttpServer/SecondBotHttpServer.cs deleted file mode 100644 index ec64309..0000000 --- a/CLI/HttpServer/SecondBotHttpServer.cs +++ /dev/null @@ -1,277 +0,0 @@ -using BetterSecondBot.HttpWebUi; -using BetterSecondBotShared.IO; -using BetterSecondBotShared.Json; -using BetterSecondBotShared.logs; -using BetterSecondBotShared.Static; -using BSB; -using BSB.bottypes; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; - -namespace BetterSecondBot.HttpServer -{ - public class Json_http_reply - { - public bool status; - public string message; - public string redirect; - } - public class SecondBotHttpServer - { - protected HttpListener listener; - protected SecondBot Bot; - public SecondBot GetBot { get { return Bot; } } - protected JsonConfig Config; - public JsonConfig GetConfig { get { return Config; } } - protected HTTPCommandsInterface post_controler; - - protected string url = ""; - public bool ShutdownHTTP { get; set; } - public bool HTTPCnCmode { get; set; } - - public string GetStatus() - { - string reply = ""; - if(Bot == null) - { - reply += "- No bot"; - } - else - { - Bot.LastStatusMessage = Bot.GetStatus(); - reply += Bot.LastStatusMessage; - } - if(listener == null) - { - reply += " - No HTTP listener"; - } - return reply; - } - public void NewBot() - { - Bot = new SecondBot(); - } - public void KillBot() - { - Bot.GetClient.Network.Logout(); - Bot.GetClient.Network.Shutdown(OpenMetaverse.NetworkManager.DisconnectType.ClientInitiated); - Bot = null; - } - public void StartHttpServer(JsonConfig setConfig) - { - StartHttpServer(setConfig, null); - } - public void StartHttpServer(SecondBot LinkBot, JsonConfig setConfig) - { - StartHttpServer(setConfig, LinkBot); - } - protected void StartHttpServer(JsonConfig setConfig, SecondBot LinkBot) - { - Config = setConfig; - Bot = LinkBot; - int port = Math.Abs(Config.Http_Port); - url = Config.Http_Host; - if (Config.Http_Host == "docker") - { - url = "http://*:" + port.ToString() + "/"; - } - else - { - if (url.StartsWith("http://") == false) - { - url = "http://localhost:" + port.ToString() + ""; - } - if (url.EndsWith("/") == false) - { - url = "" + url + "/"; - } - } - OpenHttpService(); - } - - protected async Task HandleIncomingConnections() - { - Webui myUI = new Webui(Config,Bot); - while (Bot.KillMe == false) - { - HttpListenerContext ctx = await listener.GetContextAsync().ConfigureAwait(true); - HttpListenerRequest req = ctx.Request; - HttpListenerResponse resp = ctx.Response; - resp.Cookies = req.Cookies; - string test = req.Url.AbsoluteUri; - if (helpers.notempty(Config.Http_PublicUrl) == true) - { - // normal mapped - test = test.Replace(Config.Http_PublicUrl, ""); - - //rproxy weirdness - test = test.Replace(Config.Http_PublicUrl, ""); - Config.Http_PublicUrl = Config.Http_PublicUrl.Replace("http://", ""); - Config.Http_PublicUrl = Config.Http_PublicUrl.Replace("https://", ""); - test = test.Replace(Config.Http_PublicUrl, ""); - test = test.Replace(":" + Config.Http_Port.ToString(), ""); - } - Json_http_reply reply = new Json_http_reply(); - List http_args = test.Split('/', StringSplitOptions.RemoveEmptyEntries).ToList(); - byte[] data; - resp.StatusCode = 200; - resp.ContentType = "text/html"; - if (req.HttpMethod == "GET") - { - KeyValuePair replydata = myUI.Get_Process(resp, http_args); - if (replydata.Value != null) - { - resp.ContentType = replydata.Key; - data = replydata.Value; - resp.ContentLength64 = data.LongLength; - await resp.OutputStream.WriteAsync(data, 0, data.Length).ConfigureAwait(true); - resp.Close(); - } - else - { - data = Encoding.UTF8.GetBytes("error with service"); - resp.ContentLength64 = data.LongLength; - resp.ContentType = "text/html"; - await resp.OutputStream.WriteAsync(data, 0, data.Length).ConfigureAwait(true); - resp.Close(); - } - } - else if (req.HttpMethod == "POST") - { - string text; - using (var reader = new StreamReader(req.InputStream, req.ContentEncoding)) - { - text = reader.ReadToEnd(); - } - //"command = logout & args = &passkey = Kp83VZaEhcy" - string[] pairs = text.Split('&', StringSplitOptions.RemoveEmptyEntries); - Dictionary post_args = new Dictionary(); - foreach (string B in pairs) - { - string[] bits = B.Split('=', StringSplitOptions.RemoveEmptyEntries); - if (bits.Length == 2) - { - post_args.Add(bits[0].Trim(), HttpUtility.UrlDecode(bits[1].Trim())); - } - } - resp.StatusCode = 200; - if (myUI.Post_process(reply, resp, http_args, post_args) == true) - { - bool accept_request = false; - string why_request_failed = "Bad signing request"; - - string command = ""; - string arg = ""; - string signing_code; - - if (post_args.ContainsKey("command") == true) - { - command = post_args["command"]; - if (post_args.ContainsKey("args") == true) - { - arg = post_args["args"]; - } - if (post_args.ContainsKey("code") == true) - { - signing_code = post_args["code"]; - string raw = "" + command + "" + arg + "" + Config.Security_SignedCommandkey + ""; - string hashcheck = helpers.GetSHA1(raw); - if (hashcheck == signing_code) - { - accept_request = true; - } - } - else - { - why_request_failed = "Post missing arg value"; - } - } - else - { - why_request_failed = "Post missing command value"; - } - reply.message = why_request_failed; - reply.status = accept_request; - if (accept_request == true) - { - reply.message = String.Join("###", post_controler.Call(command, arg)); - } - } - if (reply.redirect != "") - { - if (reply.redirect != null) - { - if (reply.redirect == "/") - { - reply.redirect = Config.Http_PublicUrl; - } - else - { - reply.redirect = Config.Http_PublicUrl + reply.redirect; - } - } - - } - data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(reply)); - resp.ContentLength64 = data.LongLength; - await resp.OutputStream.WriteAsync(data, 0, data.Length).ConfigureAwait(true); - resp.Close(); - } - } - } - protected void OpenHttpService() - { - post_controler = new HTTPCommandsInterface(Bot,this); - Thread t = new Thread(delegate (object _) - { - bool ok = true; - try - { - listener = new HttpListener(); - listener.Prefixes.Add(url); - listener.Start(); - } - catch (Exception e) - { - ok = false; - LogFormater.Crit("Unable to setup http service: "+e.Message+""); - LogFormater.Crit("if running on windows please check: https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/configuring-http-and-https?redirectedfrom=MSDN"); - } - if (ok == true) - { - bool clean_exit = false; - LogFormater.Status("Listening for connections on " + url + ""); - while (clean_exit == false) - { - try - { - LogFormater.Status("HTTP ready"); - Task listenTask = HandleIncomingConnections(); - listenTask.GetAwaiter().GetResult(); - } - catch - { - LogFormater.Status("HTTP died"); - //Status("Http interface killed itself: " + e.ToString() + ""); - } - } - } - listener.Close(); - }) - { - Name = "http thead", - IsBackground = true - }; - t.Start(); - } - } -} diff --git a/CLI/HttpServer/control/Actions/logout.cs b/CLI/HttpServer/control/Actions/logout.cs deleted file mode 100644 index 76f314d..0000000 --- a/CLI/HttpServer/control/Actions/logout.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace BetterSecondBot.HttpServer.Control.Actions -{ - public class Logout : HTTP_commands - { - public override string Helpfile { get { return "Makes the bot logout
Returns OK,I dont want to go if accepted"; } } - - public override string[] CallFunction(string[] args) - { - if (bot != null) - { - if (httpserver.HTTPCnCmode == true) - { - httpserver.KillBot(); - return new [] { "OK", "Bot Shutting down" }; - } - else - { - bot.KillMePlease(); - return new [] { "OK", "I dont want to go" }; - } - } - else - { - return Failed("No bot logged in"); - } - } - } -} diff --git a/CLI/HttpServer/control/Interface/cmd_lib.cs b/CLI/HttpServer/control/Interface/cmd_lib.cs deleted file mode 100644 index 5dccba1..0000000 --- a/CLI/HttpServer/control/Interface/cmd_lib.cs +++ /dev/null @@ -1,42 +0,0 @@ -using OpenMetaverse; -using System; -using System.Collections.Generic; -using System.Text; - -namespace BetterSecondBot.HttpServer.Control.Interface -{ - public class CoreCMD : HTTP_commands - { - public override string[] ArgTypes { get { return new [] { "Text","Text" }; } } - public override string[] ArgHints { get { return new [] { "Command","Args (~#~ split)" }; } } - public override string Helpfile { get { return "Calls the Commands interface via HTTP (returns OK if not rejected)"; } } - - public override int Min_Required_args { get { return 2; } } - - public override string[] CallFunction(string[] args) - { - if (bot != null) - { - if (ArgsCheck(args) == true) - { - if (bot.GetCommandsInterface.Call(args[0], args[1], UUID.Zero) == true) - { - return new [] { "OK" }; - } - else - { - return Failed("Something went wrong with the call"); - } - } - else - { - return Failed("Incorrect number of args"); - } - } - else - { - return Failed("No bot"); - } - } - } -} diff --git a/CLI/HttpServer/http_commands.cs b/CLI/HttpServer/http_commands.cs deleted file mode 100644 index 65e08d6..0000000 --- a/CLI/HttpServer/http_commands.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using OpenMetaverse; -using BSB; -using BSB.bottypes; -using BetterSecondBotShared.API; -using BetterSecondBotShared.Json; -using BetterSecondBotShared.logs; - -namespace BetterSecondBot.HttpServer -{ - public class HTTPCommandsInterface : API_supported_interface - { - protected SecondBot bot; - protected SecondBotHttpServer httpserver; - - public HTTPCommandsInterface(SecondBot linktobot, SecondBotHttpServer sethttpserver) - { - bot = linktobot; - httpserver = sethttpserver; - API_type = typeof(HTTP_commands); - LoadCommandsList(); - } - - public string[] Call(string command, string arg) - { - command = command.ToLowerInvariant(); - HTTP_commands cmd = (HTTP_commands)GetCommand(command); - if (cmd != null) - { - cmd.Setup(bot,httpserver); - return cmd.CallFunction(arg.Split(new[] { "|||" }, StringSplitOptions.None)); - } - else - { - LogFormater.Debug("" + command + " [Failed]: I have no fucking idea what you are talking about"); - return new[] { "Failed", "I have no fucking idea what you are talking about" }; - } - } - } - - public abstract class HTTP_commands : API_interface - { - protected SecondBot bot; - protected SecondBotHttpServer httpserver; - public void Setup(SecondBot setBot,SecondBotHttpServer Sethttpserver) - { - bot = setBot; - httpserver = Sethttpserver; - } - public virtual string[] CallFunction(string[] args) - { - if (ArgsCheck(args) == true) - { - return new[] { "OK", "No override" }; - } - return Failed("Incorrect number of args"); - } - protected bool ArgsCheck(string[] args) - { - if (args.Length >= Min_Required_args) - { - return true; - } - return false; - } - protected string[] Failed(string why_failed) - { - LogFormater.Warn("HTTP: " + CommandName + " [Failed]: " + why_failed); - return new [] {"Failed",why_failed}; - } - } - - -} diff --git a/CLI/HttpService/HttpAsService.cs b/CLI/HttpService/HttpAsService.cs new file mode 100644 index 0000000..3f85bab --- /dev/null +++ b/CLI/HttpService/HttpAsService.cs @@ -0,0 +1,164 @@ +using BetterSecondBotShared.Json; +using BSB.bottypes; +using System; +using EmbedIO; +using EmbedIO.Actions; +using EmbedIO.WebApi; +using System.Threading; +using Swan.Logging; +using System.Collections.Generic; +using EmbedIO.Security; +using BetterSecondBotShared.Static; +using System.Net; +using EmbedIO.Routing; + +namespace BetterSecondBot.HttpService +{ + public class HttpAsService + { + protected JsonConfig Config; + protected SecondBot Bot; + protected TokenStorage Tokens; + public HttpAsService(SecondBot MainBot, JsonConfig BotConfig) + { + Bot = MainBot; + Config = BotConfig; + + // Our web server is disposable + Logger.UnregisterLogger(); + using (var server = CreateWebServer(Config.Http_Host)) + { + // Once we've registered our modules and configured them, we call the RunAsync() method. + server.RunAsync(); + while(Bot.KillMe == false) + { + Thread.Sleep(1000); + } + } + } + + private WebServer CreateWebServer(string url) + { + Tokens = new TokenStorage(); + var server = new WebServer(o => o + .WithUrlPrefix(url) + .WithMode(HttpListenerMode.EmbedIO)) + .WithIPBanning(o => o + .WithMaxRequestsPerSecond(10) + .WithRegexRules("HTTP exception 404") + ) + .WithWebApi("/inventory", m => m + .WithController(() => new SecondbotInventory(Bot, Tokens))) + .WithWebApi("/core", m => m + .WithController(() => new SecondbotCoreWebAPi(Config, Bot, Tokens))) + .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" }))); + return server; + } + + } + + public class TokenStorage + { + protected Dictionary tokens = new Dictionary(); + + public void PurgeExpired() + { + List oldTokens = new List(); + long now = helpers.UnixTimeNow(); + foreach (KeyValuePair entry in tokens) + { + if(entry.Value.Expires <= now) + { + oldTokens.Add(entry.Key); + } + } + foreach(string A in oldTokens) + { + tokens.Remove(A); + } + } + + public string Expire(string token) + { + if (tokens.ContainsKey(token) == true) + { + if(tokens.Remove(token) == true) + { + return "ok"; + } + } + return "Failed to remove token"; + } + + public bool Allow(string code, IPAddress IPaddress) + { + if(tokens.ContainsKey(code) == true) + { + tokenInfo info = tokens[code]; + if (info.Expires <= helpers.UnixTimeNow()) + { + PurgeExpired(); + return false; + } + if (info.IP == IPaddress) + { + return true; + } + } + return false; + } + + public string CreateToken(IPAddress IP) + { + PurgeExpired(); + bool used = true; + string last = ""; + while(used == true) + { + last = helpers.GetSHA1(last + helpers.UnixTimeNow() + new Random().Next(13256).ToString()).Substring(0, 10); + used = tokens.ContainsKey(last); + if (used == false) + { + tokenInfo Info = new tokenInfo(); + Info.IP = IP; + Info.Expires = helpers.UnixTimeNow() + (60 * 10); + } + } + return last; + } + } + + public class WebApiControllerWithTokens : WebApiController + { + protected TokenStorage tokens; + protected SecondBot bot; + + public WebApiControllerWithTokens(SecondBot mainbot, TokenStorage setuptokens) + { + bot = mainbot; + tokens = setuptokens; + } + + /* + * not yet supported but will repace the current way soon(TM) + [Route(HttpVerbs.Post, "/command/{token}")] + public string CommandLib(string token, [JsonData] CommandLibCall request) + { + return "ok"; + } + */ + + public IPAddress getClientIP() + { + return HttpContext.Request.RemoteEndPoint.Address; + } + + } + + public class tokenInfo + { + public long Expires = 0; + public IPAddress IP = null; + } + +} diff --git a/CLI/HttpService/Inventory.cs b/CLI/HttpService/Inventory.cs new file mode 100644 index 0000000..25c4865 --- /dev/null +++ b/CLI/HttpService/Inventory.cs @@ -0,0 +1,44 @@ +using BSB.bottypes; +using BSB.Static; +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; +using System; +using System.Collections.Generic; +using System.Text; +using OpenMetaverse; + +namespace BetterSecondBot.HttpService +{ + public class SecondbotInventory : WebApiControllerWithTokens + { + public SecondbotInventory(SecondBot mainbot, TokenStorage setuptokens) : base(mainbot, setuptokens) { } + + [Route(HttpVerbs.Get, "/folders/{token}")] + public string folders(string token) + { + if(tokens.Allow(token, getClientIP()) == true) + { + return HelperInventory.MapFolderJson(bot); + } + return "Token not accepted"; + } + + [Route(HttpVerbs.Get, "/contents/{folderUUID}/{token}")] + public string Contents(string folderUUID, string token) + { + if (tokens.Allow(token, getClientIP()) == true) + { + if (UUID.TryParse(folderUUID, out UUID folder) == true) + { + return HelperInventory.MapFolderInventoryJson(bot, folder); + } + return "Invaild folder UUID"; + } + return "Token not accepted"; + + } + } + + +} diff --git a/CLI/HttpService/Secondbot.cs b/CLI/HttpService/Secondbot.cs new file mode 100644 index 0000000..8f3497b --- /dev/null +++ b/CLI/HttpService/Secondbot.cs @@ -0,0 +1,107 @@ +using BetterSecondBotShared.Json; +using BetterSecondBotShared.Static; +using BSB.bottypes; +using OpenMetaverse; +using System.Threading.Tasks; + +using EmbedIO; +using EmbedIO.Routing; +using EmbedIO.WebApi; + + +namespace BetterSecondBot.HttpService +{ + public class SecondbotCoreWebAPi : WebApiControllerWithTokens + { + + protected JsonConfig config; + public SecondbotCoreWebAPi(JsonConfig myconfig, SecondBot mainbot, TokenStorage setuptokens) : base(mainbot, setuptokens) + { + config = myconfig; + } + + [Route(HttpVerbs.Post, "/gettoken")] + public string GetToken([FormField] string authcode) + { + long now = helpers.UnixTimeNow() + 120; + int loop = 0; + bool accepted = false; + while(loop <= 30) + { + string hashA = helpers.GetSHA1((now + loop) + "" + config.Security_WebUIKey).Substring(0, 10); + string hashB = helpers.GetSHA1((now - loop) + "" + config.Security_WebUIKey).Substring(0, 10); + if ((authcode == hashA) || (authcode == hashB)) + { + accepted = true; + break; + } + loop++; + } + if(accepted == true) + { + return tokens.CreateToken(HttpContext.Request.RemoteEndPoint.Address); + } + return "Authcode not accepted"; + } + + [Route(HttpVerbs.Get, "/logout/{token}")] + public string Logout(string token) + { + if (tokens.Allow(token, getClientIP()) == true) + { + return tokens.Expire(token); + } + return "Token not accepted"; + } + + [Route(HttpVerbs.Get, "/version/{token}")] + public string Version(string token) + { + if (tokens.Allow(token, getClientIP()) == true) + { + return bot.MyVersion; + } + return "Token not accepted"; + } + + [Route(HttpVerbs.Get, "/name/{token}")] + public string Name(string token) + { + if (tokens.Allow(token, getClientIP()) == true) + { + return bot.GetClient.Self.FirstName + " " + bot.GetClient.Self.LastName; + } + return "Token not accepted"; + } + + [Route(HttpVerbs.Post, "/command/{token}")] + public async Task command(string token) + { + if (tokens.Allow(token, getClientIP()) == true) + { + CommandLibCall data = await HttpContext.GetRequestDataAsync(); + string compressedArgs = string.Join("~#~", data.Args); + string raw = data.Command + compressedArgs; + string cooked = helpers.GetSHA1(raw + config.Security_SignedCommandkey); + if (cooked == data.AuthCode) + { + bool status = bot.GetCommandsInterface.Call(data.Command, compressedArgs, UUID.Zero, "~#~"); + if (status == true) + { + return "accepted"; + } + } + } + return "Token not accepted"; + } + } + + public class CommandLibCall + { + public string Command { get; set; } + public string[] Args { get; set; } + public string AuthCode { get; set; } + } + + +} diff --git a/CLI/HttpWebUi/theme/css/site.css b/CLI/HttpWebUi/theme/css/site.css deleted file mode 100644 index 582030c..0000000 --- a/CLI/HttpWebUi/theme/css/site.css +++ /dev/null @@ -1,4 +0,0 @@ -.logo -{ - width: 100%; -} diff --git a/CLI/HttpWebUi/theme/images/favicon.png b/CLI/HttpWebUi/theme/images/favicon.png deleted file mode 100644 index dc83cd083845b1897b461451512579d8e6c47bb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4572 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 diff --git a/CLI/HttpWebUi/theme/images/logo.png b/CLI/HttpWebUi/theme/images/logo.png deleted file mode 100644 index 540dbeac7ce977451c31f576609179533669f0d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28566 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

m~c-ze4G0IB@3eM}I>pli20-WZGpul8L zt>tS*2v`r%{_BrujFRWIutmr3eBt{hY8c8VEAIOs^k=E_|R?+J&Sq51igZ=kh1-qE{LaJAc+2SS&`qIkvt^2%FalpoDi_ z|C!f|i7ie0tb&XCWGs3+^tnMNz~dCl2?{WC6ehQH)z5|a79k|L;D8Pv@}#}%O>&0G z3jK8*9i2` - - diff --git a/CLI/HttpWebUi/theme/layout/cdn.js.layout b/CLI/HttpWebUi/theme/layout/cdn.js.layout deleted file mode 100644 index a47b628..0000000 --- a/CLI/HttpWebUi/theme/layout/cdn.js.layout +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/CLI/HttpWebUi/theme/layout/full.layout b/CLI/HttpWebUi/theme/layout/full.layout deleted file mode 100644 index 236b6d2..0000000 --- a/CLI/HttpWebUi/theme/layout/full.layout +++ /dev/null @@ -1,35 +0,0 @@ - - - [[html_title]] - [[html_title_after]] - [[html_cs_top_cdn]] - [[html_cs_top]] - - - - -

-
-
-
-
- -
-
-
-
- [[page_content]] -
-
-
-
-
- [[html_js_bottom_cdn]] - [[html_js_bottom]] - - - - diff --git a/CLI/HttpWebUi/theme/layout/login.block b/CLI/HttpWebUi/theme/layout/login.block deleted file mode 100644 index 8a26ea4..0000000 --- a/CLI/HttpWebUi/theme/layout/login.block +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
Bot webUI auth required
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CLI/HttpWebUi/theme/layout/sidemenu.layout b/CLI/HttpWebUi/theme/layout/sidemenu.layout deleted file mode 100644 index 057fd2e..0000000 --- a/CLI/HttpWebUi/theme/layout/sidemenu.layout +++ /dev/null @@ -1,61 +0,0 @@ - - - [[html_title]] - [[html_title_after]] - [[html_cs_top_cdn]] - [[html_cs_top]] - - - - -
-
-
-
-
-
-
-
- -
-
-
-
-

-
-
-
-
-
- -
-
-
- - -
-
-
-
-
[[page_title]]
-
[[page_actions]]
-
-
-
- [[page_content]] -
-
-
-
-
- [[html_js_bottom_cdn]] - [[html_js_bottom]] - - - - diff --git a/CLI/HttpWebUi/webui.cs b/CLI/HttpWebUi/webui.cs deleted file mode 100644 index a58463d..0000000 --- a/CLI/HttpWebUi/webui.cs +++ /dev/null @@ -1,514 +0,0 @@ -using BetterSecondBot.HttpServer; -using BetterSecondBotShared.Json; -using BetterSecondBotShared.Static; -using BSB.bottypes; -using Microsoft.VisualBasic.CompilerServices; -using Newtonsoft.Json; -using OpenMetaverse; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Text; -using System.Threading; - -namespace BetterSecondBot.HttpWebUi -{ - public class Webui - { - private JsonConfig siteconfig; - private SecondBot bot; - private Dictionary vaildlogincookies = new Dictionary(); - public Webui(JsonConfig setconfig, SecondBot setbot) - { - bot = setbot; - siteconfig = setconfig; - } - protected KeyValuePair Logincookievaild(HttpListenerResponse resp) - { - return Logincookievaild(resp, false); - } - protected KeyValuePair Logincookievaild(HttpListenerResponse resp, bool force_logout) - { - foreach (Cookie C in resp.Cookies) - { - if (C.Name == "logincookie") - { - if (force_logout == true) - { - if (vaildlogincookies.ContainsKey(C.Value) == true) - { - vaildlogincookies.Remove(C.Value); - } - } - else - { - if (vaildlogincookies.ContainsKey(C.Value) == true) - { - if (vaildlogincookies[C.Value] > helpers.UnixTimeNow()) - { - return new KeyValuePair(true, true); - } - } - } - resp.Cookies.Remove(C); - C.Expired = true; - resp.Cookies.Add(C); - return new KeyValuePair(true, false); - } - } - return new KeyValuePair(false, false); - } - public bool Post_process(Json_http_reply reply, HttpListenerResponse resp, List args, Dictionary post_args) - { - bool processed = false; - if (args != null) - { - if (args.Count >= 1) - { - if (args[0] == "ajax") - { - processed = true; - // webui only cares about ajax post requests - string mod = "none"; - string area = "none"; - if (args.Count >= 2) - { - mod = args[1]; - } - if (args.Count >= 3) - { - area = args[2]; - } - KeyValuePair logincheck = Logincookievaild(resp); - - if (logincheck.Key == false) - { - if (mod == "login") - { - if (post_args.ContainsKey("logincode") == true) - { - if (post_args["logincode"] == siteconfig.Security_WebUIKey) - { - reply.status = true; - string newcookiecode = ""; - while ((newcookiecode == null) || (newcookiecode == "") || (vaildlogincookies.ContainsKey(newcookiecode) == true)) - { - newcookiecode = helpers.GetSHA1(siteconfig.Security_WebUIKey + helpers.UnixTimeNow().ToString() + new Random().Next(12345).ToString()); - } - vaildlogincookies.Add(newcookiecode, helpers.UnixTimeNow() + (5 * 60)); // auto logout after 5 mins - Cookie logincookie = new Cookie("logincookie", newcookiecode) - { - Expires = DateTime.Now.AddDays(1), - Path = "/" - }; - resp.Cookies.Add(logincookie); - Console.WriteLine("Cookie created: " + logincookie.Value + " <=> " + newcookiecode + ""); - reply.message = "ok"; - reply.redirect = "/"; - } - else - { - reply.status = false; - reply.message = "fail"; - } - } - else - { - reply.status = false; - reply.message = "Missing code"; - } - } - else - { - reply.status = false; - reply.message = "No access"; - } - } - else - { - if (logincheck.Value == true) - { - reply.status = true; - // logged in - if (mod == "logout") - { - Logincookievaild(resp, true); - reply.message = "logged-out"; - reply.redirect = "/"; - reply.status = false; - } - else if(mod == "interface") - { - if (area == "get") - { - reply.message = JsonConvert.SerializeObject(bot.GetLastCommands(30)); - } - else if(area == "send") - { - if (post_args.ContainsKey("message") == true) - { - string[] bits = post_args["message"].Split("|||"); - if(bits.Length == 2) - { - reply.status = bot.GetCommandsInterface.Call(bits[0], bits[1]); - if(reply.status == true) - { - reply.message = "ok"; - } - else - { - reply.message = "Failed"; - } - } - else - { - reply.status = bot.GetCommandsInterface.Call(post_args["message"]); - if (reply.status == true) - { - reply.message = "ok"; - } - else - { - reply.message = "Failed"; - } - } - } - else - { - reply.message = "No message"; - reply.status = false; - } - } - else - { - reply.message = "Unknown area"; - reply.status = false; - } - } - else if (mod == "localchat") - { - if (area == "get") - { - reply.message = JsonConvert.SerializeObject(bot.getLocalChatHistory()); - } - else if (area == "send") - { - if (post_args.ContainsKey("message") == true) - { - bot.AddToLocalChat("{Via WebGUI}", post_args["message"]); - bot.GetClient.Self.Chat("{Via WebGUI} "+ post_args["message"], 0,ChatType.Normal); - reply.message = "ok"; - reply.status = true; - } - else - { - reply.message = "No message"; - reply.status = false; - } - } - else - { - reply.message = "Unknown area"; - reply.status = false; - } - } - else if (mod == "groupchat") - { - if (area == "list") - { - Dictionary groupname_uuid = new Dictionary(); - foreach (KeyValuePair entry in bot.MyGroups) - { - groupname_uuid.Add(entry.Key.ToString(), entry.Value.Name); - } - reply.message = JsonConvert.SerializeObject(groupname_uuid); - } - if (area == "get") - { - if (post_args.ContainsKey("groupuuid") == true) - { - if (UUID.TryParse(post_args["groupuuid"], out UUID groupuuid) == true) - { - reply.message = JsonConvert.SerializeObject(bot.GetGroupchat(groupuuid)); - } - else - { - reply.message = "Unable to process group uuid"; - reply.status = false; - } - } - else - { - reply.message = "No groupuuid"; - reply.status = false; - } - } - else if (area == "send") - { - if (post_args.ContainsKey("groupuuid") == true) - { - if (UUID.TryParse(post_args["groupuuid"], out UUID groupuuid) == true) - { - if (post_args.ContainsKey("message") == true) - { - bot.GetClient.Self.RequestJoinGroupChat(groupuuid); - Thread.Sleep(100); - bot.GetClient.Self.InstantMessageGroup(groupuuid, "{Via WebGUI} " + post_args["message"]); - bot.AddToGroupchat(groupuuid, "{Via WebGUI}", post_args["message"]); - reply.message = "ok"; - reply.status = true; - } - else - { - reply.message = "No message"; - reply.status = false; - } - } - else - { - reply.message = "Unable to process group uuid"; - reply.status = false; - } - } - else - { - reply.message = "No groupuuid"; - reply.status = false; - } - } - else - { - reply.message = "Unknown area"; - reply.status = false; - } - } - else if (mod == "im") - { - if (area == "list") - { - reply.message = JsonConvert.SerializeObject(bot.GetIMChatWindowKeyNames()); - } - else if (area == "send") - { - if (post_args.ContainsKey("avataruuid") == true) - { - if (UUID.TryParse(post_args["avataruuid"], out UUID avataruuid) == true) - { - if (post_args.ContainsKey("message") == true) - { - bot.GetCommandsInterface.Call("im", "" + avataruuid.ToString() + "~#~{Via WebGUI} " + post_args["message"]); - reply.message = "ok"; - reply.status = true; - } - else - { - reply.message = "No message"; - - } - } - else - { - reply.message = "Unable to process avatar uuid"; - reply.status = false; - } - } - else - { - reply.message = "No avataruuid"; - reply.status = false; - } - } - else if (area == "get") - { - if (post_args.ContainsKey("avataruuid") == true) - { - if (UUID.TryParse(post_args["avataruuid"], out UUID avataruuid) == true) - { - reply.message = JsonConvert.SerializeObject(bot.GetIMChatWindow(avataruuid)); - } - else - { - reply.message = "Unable to process avatar uuid"; - reply.status = false; - } - } - else - { - reply.message = "No avataruuid"; - reply.status = false; - } - } - else - { - reply.message = "Unknown area"; - reply.status = false; - } - } - else - { - reply.message = "No action"; - reply.status = false; - } - } - else - { - // login expired - reply.message = "login-expired"; - reply.redirect = "/"; - Logincookievaild(resp, true); - } - } - } - } - } - return !processed; - } - public string Html_logged_in(string mod, string area) - { - string layout = helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), "sidemenu.layout"); - Dictionary swaps = new Dictionary(); - - - string pagetitle = "Dashboard"; - string pagecontent = ""; - string pageactions = ""; - string js_onready = ""; - string[] menu_text = new string[] { "Localchat", "Groupchat", "IMs", "Interface" }; - string[] menu_links = new string[] { "localchat", "groupchat", "im", "interface" }; - string[] meni_icons = new string[] { "fas fa-comment", "fas fa-comments", "fas fa-users","fas fa-robot" }; - int loop = 0; - string menu = ""; - while (loop < menu_text.Count()) - { - string active = ""; - if (menu_links[loop] == mod) active = "active"; - menu = "" + menu + "
  • " + menu_text[loop] + "
  • "; - loop++; - } - swaps.Add("html_menu", menu); - if (mod == "none") - { - pagecontent = "

    Please select a menu item on the left

    "; - } - else if(mod == "localchat") - { - pagetitle = "Localchat"; - pagecontent = "
    " + - "
    " + - "" + - ""+ - "
    "; - js_onready = "setInterval(function(){ update_localchat(\"" + siteconfig.Http_PublicUrl+"\"); },400);"; - - } - else if (mod == "groupchat") - { - pagetitle = "Group chat"; - pagecontent = ""; - if (area == "none") - { - - } - - - - } - swaps.Add("html_title", pagetitle); - swaps.Add("page_actions", pageactions); - swaps.Add("page_content", pagecontent); - swaps.Add("page_title", pagetitle); - swaps.Add("html_cs_top", ""); - swaps.Add("html_js_bottom", ""); - swaps.Add("html_js_onready", js_onready); - foreach (KeyValuePair A in swaps) - { - layout = layout.Replace("[[" + A.Key + "]]", A.Value); - } - return layout; - } - public string Html_logged_out() - { - string layout = helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), "full.layout"); - layout = layout.Replace("[[page_content]]", helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), "login.block")); - layout = layout.Replace("[[html_js_onready]]", ""); - layout = layout.Replace("[[html_cs_top]]", ""); - layout = layout.Replace("[[html_js_bottom]]", ""); - layout = layout.Replace("[[html_title]]", siteconfig.Basic_BotUserName); - return layout; - } - public KeyValuePair Get_Process(HttpListenerResponse resp, List args) - { - - // get content - if (args != null) - { - if (args.Count >= 1) - { - if (args[0] == "theme") - { - if (args.Count >= 2) - { - if (args[1] == "images") - { - return new KeyValuePair("image/png", helpers.ReadResourceFileBinary(Assembly.GetExecutingAssembly(), args[2])); - } - else if (args[1] == "css") - { - return new KeyValuePair("text/css", Encoding.UTF8.GetBytes(helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), args[2]))); - } - else if (args[1] == "js") - { - return new KeyValuePair("text/javascript", Encoding.UTF8.GetBytes(helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), args[2]))); - } - } - } - } - } - // get display - KeyValuePair logincheck = Logincookievaild(resp); - if (logincheck.Key == true) - { - if (logincheck.Value == false) - { - Logincookievaild(resp, true); - } - } - string reply_with; - if (logincheck.Value == false) - { - reply_with = Html_logged_out(); - } - else - { - string mod = "none"; - string area = "none"; - if (args.Count >= 1) - { - mod = args[0]; - } - if (args.Count >= 2) - { - area = args[1]; - } - - if (mod == "logout") - { - Logincookievaild(resp, true); - reply_with = Html_logged_out(); - } - else - { - // logged in - reply_with = Html_logged_in(mod, area); - } - } - reply_with = reply_with.Replace("[[url_base]]", siteconfig.Http_PublicUrl); - reply_with = reply_with.Replace("[[html_title_after]]", "Secondbot web UI"); - reply_with = reply_with.Replace("[[html_cs_top_cdn]]", helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), "cdn.css.layout")); - reply_with = reply_with.Replace("[[html_js_bottom_cdn]]", helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), "cdn.js.layout")); - return new KeyValuePair("text/html", Encoding.UTF8.GetBytes(reply_with)); - } - } -} diff --git a/CLI/WikiMake/wiki_header.txt b/CLI/WikiMake/wiki_header.txt index 28a6b76..8e3fcf5 100644 --- a/CLI/WikiMake/wiki_header.txt +++ b/CLI/WikiMake/wiki_header.txt @@ -1,28 +1,28 @@ - - -Secondbot Wiki [[AREA]] - - - - - + + +Secondbot Wiki [[AREA]] + + + + +
    \ No newline at end of file diff --git a/CLI/WikiMake/wikimake.cs b/CLI/WikiMake/wikimake.cs index 008cf95..8a4b531 100644 --- a/CLI/WikiMake/wikimake.cs +++ b/CLI/WikiMake/wikimake.cs @@ -17,7 +17,7 @@ namespace BetterSecondBot.WikiMake protected string html_footer = ""; protected string buildVersion = ""; protected SimpleIO io; - protected Dictionary seen_command_names = new Dictionary(); + protected Dictionary seen_command_names = new Dictionary(); public string CreateRoot() @@ -28,7 +28,7 @@ namespace BetterSecondBot.WikiMake sb.Append(buildVersion); sb.Append("
    "); sb.Append(""); - foreach (KeyValuePair> entry in api_reports) + foreach (KeyValuePair> entry in api_reports) { sb.Append(""); sb.Append("
    InterfaceCommandsStatus
    [[AREA]]"); - if(workspace != "") + if (workspace != "") { sb.Append(" / [[WORKSPACE]]"); } @@ -103,7 +102,7 @@ namespace BetterSecondBot.WikiMake int minargs = shared_interface.GetCommandArgs(c); string[] arg_types = shared_interface.GetCommandArgTypes(c); string[] arg_hints = shared_interface.GetCommandArgHints(c); - if(area == "Core") + if (area == "Core") { StringBuilder ExampleCall = new StringBuilder(); ExampleCall.Append("Example: [[COMMAND]]"); @@ -194,9 +193,9 @@ namespace BetterSecondBot.WikiMake sb.Append(buildVersion); sb.Append("
    "); sb.Append(""); - foreach(string c in cmds) + foreach (string c in cmds) { - if(shared_interface.GetCommandWorkspace(c) == workspace) + if (shared_interface.GetCommandWorkspace(c) == workspace) { int args = shared_interface.GetCommandArgs(c); sb.Append("
    CommandMin args
    Build: "); + sb.Append(buildVersion); + sb.Append("
    "); + sb.Append("

    Interface: [[AREA]]"); + if (workspace != "") + { + sb.Append(" / [[WORKSPACE]]"); + } + sb.Append("


    [[COMMAND]]

    "); + sb.Append("[[URLENDPOINT]]/[[WORKSPACE]]/[[COMMAND]]/[[URLADDON]]
    "); + sb.Append("Method: [[APIMETHOD]]"); + sb.Append("
    "); + sb.Append("[[HELP]]"); + Dictionary> values = HTTP.getCommandArgs(workspace, c); + int ValueCount = values.Count; + + if (ValueCount > 0) + { + sb.Append("

    Args helper

    "); + sb.Append(""); + foreach (KeyValuePair> entry in values) + { + string hint = entry.Value.Value; + string type = entry.Value.Key; + if (entry.Value.Key.Contains("Optional") == true) + { + type = type.Replace("Optional", "{Optional} "); + } + else if (entry.Value.Key == "URLARG") + { + type = "URL arg"; + } + sb.Append(""); + } + sb.Append("
    NameTypeHint
    " + entry.Key + "" + type + "" + hint + "
    "); + } + + string[] returnvalues = HTTP.getReturnsValues(workspace, c); + if (returnvalues.Length > 0) + { + sb.Append("

    Possible replys

    "); + sb.Append("
      "); + foreach (string entry in returnvalues) + { + sb.Append("
    • "+entry+"
    • "); + } + sb.Append("
    "); + } + + + + sb = DebugModeCreateWiki.MenuActive(sb, area); + sb.Append(html_footer); + sb.Replace("[[COMMAND]]", c); + sb.Replace("[[URLENDPOINT]]", "http://localhost:8080"); + sb.Replace("[[APIMETHOD]]", HTTP.getCommandMethod(workspace, c)); + sb.Replace("[[URLADDON]]", getURLargs(values)); + sb.Replace("[[HELP]]", HTTP.getCommandAbout(workspace, c)); + sb.Replace("[[MINARGS]]", ValueCount.ToString()); + sb.Replace("[[WORKSPACE]]", workspace); + sb.Replace("[[AREA]]", area); + sb.Replace("[[SUBFOLDER]]", ""); + sb.Replace("[[RETURNROOT]]", "../"); + string target_file = "" + area + "" + workspace + "" + c + ".html"; + io.writefile(target_file, sb.ToString()); + } + } + } + + protected void HTTPmenu(string area, HTTPendpoint HTTP) + { + api_reports.Add(area, new KeyValuePair(HTTP.getFullCount(), "Basic")); + + // create index + StringBuilder sb = new StringBuilder(); + sb.Append(html_header); + sb.Append("

    Interface:"); + sb.Append("HTTP"); + sb.Append("


    "); + sb.Append("

    Build: "); + sb.Append(buildVersion); + sb.Append("


    "); + sb.Append(""); + foreach (string workspace in HTTP.getEndPoints()) + { + string workspace_link = "" + workspace + ""; + sb.Append(""); + } + sb.Append("
    WorkspaceCommands
    "); + sb.Append(workspace_link); + sb.Append(""); + sb.Append(HTTP.getEndpointCount(workspace).ToString()); + sb.Append("
    "); + sb.Replace("[[SUBFOLDER]]", ""); + sb.Replace("[[RETURNROOT]]", "../"); + sb.Replace("[[AREA]]", area); + sb.Append(html_footer); + sb = DebugModeCreateWiki.MenuActive(sb, area); + io.writefile("" + area + ".html", sb.ToString()); + } + + protected void HTTPWorkspaces(string area, HTTPendpoint HTTP) + { + foreach (string workspace in HTTP.getEndPoints()) + { + StringBuilder sb = new StringBuilder(); + sb.Append(html_header); + sb.Append("

    Interface: [[AREA]]"); + if (workspace != "") + { + sb.Append(" / [[WORKSPACE]]"); + } + sb.Append("


    "); + sb.Append("

    Build: "); + sb.Append(buildVersion); + sb.Append("


    "); + sb.Append(""); + foreach (string c in HTTP.getEndpointCommands(workspace)) + { + int args = HTTP.getCommandArgCount(workspace, c); + sb.Append(""); + } + sb.Append("
    CommandMin args
    "); + sb.Append(c); + sb.Append(""); + sb.Append(args.ToString()); + sb.Append("
    "); + sb.Replace("[[SUBFOLDER]]", ""); + sb.Replace("[[RETURNROOT]]", "../"); + sb.Replace("[[WORKSPACE]]", workspace); + sb.Replace("[[AREA]]", area); + sb.Append(html_footer); + sb = DebugModeCreateWiki.MenuActive(sb, area); + string target_file = "" + area + "" + workspace + ".html"; + io.writefile(target_file, sb.ToString()); + } + } + } + + public class HTTPendpoint + { + protected List endpoints = new List(); + public HTTPendpoint() + { + Endpoint core = new Endpoint(); + core.name = "core"; + core.callable.Add(new gettoken()); + core.callable.Add(new logout()); + core.callable.Add(new version()); + core.callable.Add(new name()); + core.callable.Add(new command()); + endpoints.Add(core); + Endpoint inventory = new Endpoint(); + inventory.name = "inventory"; + inventory.callable.Add(new contents()); + inventory.callable.Add(new folders()); + endpoints.Add(inventory); + } + + public string getCommandMethod(string endpoint, string command) + { + foreach (Endpoint End in endpoints) + { + if (End.name == endpoint) + { + foreach (APIcall api in End.callable) + { + if (api.name == command) + { + return api.type; + } + } + } + } + return "?"; + } + + public string getCommandAbout(string endpoint, string command) + { + foreach (Endpoint End in endpoints) + { + if (End.name == endpoint) + { + foreach (APIcall api in End.callable) + { + if (api.name == command) + { + return api.about; + } + } + } + } + return "?"; + } + + public string[] getReturnsValues(string endpoint, string command) + { + foreach (Endpoint End in endpoints) + { + if (End.name == endpoint) + { + foreach (APIcall api in End.callable) + { + if (api.name == command) + { + return api.returns.ToArray(); + } + } + } + } + return new string[] { }; + } + + + + public int getCommandArgCount(string endpoint, string command) + { + foreach (Endpoint End in endpoints) + { + if (End.name == endpoint) + { + foreach (APIcall api in End.callable) + { + if (api.name == command) + { + return api.values.Count; + } + } + } + } + return 0; + } + + public string[] getEndpointCommands(string endpointname) + { + List commands = new List(); + foreach (Endpoint End in endpoints) + { + if (End.name == endpointname) + { + foreach (APIcall api in End.callable) + { + commands.Add(api.name); + } + } + } + return commands.ToArray(); + } + + public string[] getEndPoints() + { + List names = new List(); + foreach (Endpoint End in endpoints) + { + names.Add(End.name); + } + return names.ToArray(); + } + + public int getEndpointCount(string endpointname) + { + foreach (Endpoint End in endpoints) + { + if (End.name == endpointname) + { + return End.callable.Count; + } + } + return 0; + } + + public int getFullCount() + { + int count = 0; + foreach (Endpoint End in endpoints) + { + count += End.callable.Count; + } + return count; + } + + public Dictionary> getCommandArgs(string endpoint, string command) + { + Dictionary> empty = new Dictionary>(); + foreach (Endpoint End in endpoints) + { + if (End.name == endpoint) + { + foreach (APIcall api in End.callable) + { + if (api.name == command) + { + return api.values; + } + } + } + } + return empty; + + } + + } + + public class Endpoint + { + public string name = "Notset"; + public List callable = new List(); + } + + abstract public class APIcall + { + public string name { get { return GetType().Name.ToLowerInvariant(); } } + public string type = "get"; + public string about = "A API call that is missing its about value"; + public Dictionary> values = new Dictionary>(); + public List returns = new List(); + public bool RequiresToken = true; + + public APIcall() + { + Setup(); + } + public virtual void Setup() + { + if (RequiresToken == true) + { + returns.Add("Token not accepted"); + values.Add( + "token", + new KeyValuePair( + "URLARG", + "the api access token") + ); + } + } + } + + abstract public class postAPIcall : APIcall + { + public override void Setup() + { + type = "post"; + base.Setup(); + } + } + + public class gettoken : postAPIcall + { + public override void Setup() + { + RequiresToken = false; + about = "Requests a new token (Vaild for 10 mins)
    to use with all other requests"; + values.Add("Authcode", new KeyValuePair("string", "the first 10 chars of SHA1(unixtime+WebUIkey)
    unixtime can be +- 30 of the bots time.")); + returns = new List(); + returns.Add("Authcode not accepted"); + returns.Add("New API Token"); + base.Setup(); + } + } + + public class logout : APIcall + { + public override void Setup() + { + about = "A API call that is missing its about value"; + returns.Add("ok"); + returns.Add("Failed to remove token"); + base.Setup(); + } + } + + public class version : APIcall + { + public override void Setup() + { + about = "Gets the bots build version"; + returns.Add("Bot build version"); + base.Setup(); + } + } + + public class name : APIcall + { + public override void Setup() + { + about = "Gets the name of the bot"; + returns.Add("Fistname Lastname"); + base.Setup(); + } + } + + public class command : postAPIcall + { + public override void Setup() + { + about = "Makes a request to the core commands lib"; + values.Add( + "body", + new KeyValuePair( + "JsonObject", + "A JSON object formated as follows
    Command: string
    Args: string[]
    AuthCode: string
    ========
    See LSL example on how to create a core command auth code") + ); + returns.Add("accepted"); + base.Setup(); + } + } + + public class contents : APIcall + { + public override void Setup() + { + about = "Requests the contents of a folder as an array of InventoryMapItem
    Formated as follows
    InventoryMapItem
    • id: UUID
    • name: String
    • typename: String
    "; + values.Add( + "folderUUID", + new KeyValuePair( + "URLARG", + "the folder to fetch (Found via: inventory/folders)") + ); + returns.Add("array of InventoryMapItem"); + base.Setup(); + } + } + + public class folders : APIcall + { + public override void Setup() + { + about = "Requests the inventory folder layout as a json object InventoryMapFolder
    Formated as follows
    InventoryMapItem
    • id: UUID
    • name: String
    • subfolders: InventoryMapFolder[]
    "; + returns.Add("array of InventoryMapFolder"); + base.Setup(); + } + } + } diff --git a/Core/BSB.csproj b/Core/BSB.csproj index ce419aa..90e779d 100644 --- a/Core/BSB.csproj +++ b/Core/BSB.csproj @@ -21,8 +21,8 @@ false linux-x64 - - + +