HTTP and Discord updates

config values no longer used:
Http_Port
Http_PublicUrl

new config values:
DiscordFull_Keep_GroupChat
This commit is contained in:
Madpeterz 2021-01-31 19:23:20 +00:00
parent 3c4158bcf4
commit e3bb28ff8e
26 changed files with 856 additions and 1363 deletions

View file

@ -44,9 +44,7 @@ namespace BetterSecondBotShared.Json
// HTTP interface // HTTP interface
public bool Http_Enable { get; set; } public bool Http_Enable { get; set; }
public int Http_Port { get; set; }
public string Http_Host { get; set; } public string Http_Host { get; set; }
public string Http_PublicUrl { get; set; }
// Name2Key DB // Name2Key DB
public bool Name2Key_Enable { get; set; } public bool Name2Key_Enable { get; set; }

View file

@ -126,17 +126,9 @@ namespace BetterSecondBotShared.Json
{ {
return "Allow the bot to start a HTTP interface (Required for web UI and Post commands)"; 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") else if (cmd == "Http_Host")
{ {
return "The URL the interface is on example use \"docker\" for auto or \"http://localhost:portnum\""; return "The URL the interface is on example use \"docker\" for auto or \"http://*:8080\" to listen on port 8080<br/>Note: for windows you might need to accept a firewall alert and restart the app!";
}
else if (cmd == "Http_PublicUrl")
{
return "The public url used to access the web ui";
} }
return ""; return "";
} }
@ -265,15 +257,14 @@ namespace BetterSecondBotShared.Json
else if (cmd == "DiscordFull_Enable") { return "False"; } else if (cmd == "DiscordFull_Enable") { return "False"; }
else if (cmd == "DiscordFull_Token") { return ""; } else if (cmd == "DiscordFull_Token") { return ""; }
else if (cmd == "DiscordFull_ServerID") { return ""; } else if (cmd == "DiscordFull_ServerID") { return ""; }
else if (cmd == "DiscordFull_Keep_GroupChat") { return "False"; }
return ""; return "";
} }
protected string GetValueHint_HTTP(string cmd) protected string GetValueHint_HTTP(string cmd)
{ {
// HTTP // HTTP
if (cmd == "Http_Enable") { return "False"; } if (cmd == "Http_Enable") { return "False"; }
else if (cmd == "Http_Port") { return "80"; } else if (cmd == "Http_Host") { return "http://*:8080"; }
else if (cmd == "Http_Host") { return "http://localhost:80"; }
else if (cmd == "Http_PublicUrl") { return "http://localhost/"; }
return ""; return "";
} }
protected string GetValueHint_DiscordTTS(string cmd) 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"); 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)) 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 else
{ {

View file

@ -26,35 +26,18 @@
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="HttpWebUi\theme\images\favicon.png" />
<None Remove="HttpWebUi\theme\images\logo.png" />
<None Remove="HttpWebUi\theme\layout\cdn.css.layout" />
<None Remove="HttpWebUi\theme\layout\cdn.js.layout" />
<None Remove="HttpWebUi\theme\layout\full.layout" />
<None Remove="HttpWebUi\theme\layout\login.block" />
<None Remove="HttpWebUi\theme\layout\sidemenu.layout" />
<None Remove="HttpWebUi\theme\site.css" />
<None Remove="HttpWebUi\theme\site.js" />
<None Remove="WikiMake\wiki_footer.txt" /> <None Remove="WikiMake\wiki_footer.txt" />
<None Remove="WikiMake\wiki_header.txt" /> <None Remove="WikiMake\wiki_header.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EmbedIO" Version="3.4.3" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.8" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.8" />
<PackageReference Include="MSBuildGitHash" Version="2.0.1"> <PackageReference Include="MSBuildGitHash" Version="2.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="HttpWebUi\theme\images\favicon.png" />
<EmbeddedResource Include="HttpWebUi\theme\images\logo.png" />
<EmbeddedResource Include="HttpWebUi\theme\layout\cdn.css.layout" />
<EmbeddedResource Include="HttpWebUi\theme\layout\cdn.js.layout" />
<EmbeddedResource Include="HttpWebUi\theme\layout\full.layout" />
<EmbeddedResource Include="HttpWebUi\theme\layout\login.block" />
<EmbeddedResource Include="HttpWebUi\theme\layout\sidemenu.layout" />
<EmbeddedResource Include="HttpWebUi\theme\css\site.css" />
<EmbeddedResource Include="HttpWebUi\theme\js\site.js" />
<EmbeddedResource Include="WikiMake\wiki_footer.txt" /> <EmbeddedResource Include="WikiMake\wiki_footer.txt" />
<EmbeddedResource Include="WikiMake\wiki_header.txt" /> <EmbeddedResource Include="WikiMake\wiki_header.txt" />
</ItemGroup> </ItemGroup>

View file

@ -1,12 +1,8 @@
using BetterSecondBot.HttpServer; using BetterSecondBot.HttpService;
using BetterSecondBotShared.IO;
using BetterSecondBotShared.Json; using BetterSecondBotShared.Json;
using BetterSecondBotShared.logs; using BetterSecondBotShared.logs;
using BetterSecondBotShared.Static; using BetterSecondBotShared.Static;
using BSB.bottypes; using BSB.bottypes;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; using System.Threading;
using static BetterSecondBot.Program; using static BetterSecondBot.Program;
@ -51,8 +47,11 @@ namespace BetterSecondBot
Bot.Start(); Bot.Start();
if (Config.Http_Enable == true) if (Config.Http_Enable == true)
{ {
/*
SecondBotHttpServer my_http_server = new SecondBotHttpServer(); SecondBotHttpServer my_http_server = new SecondBotHttpServer();
my_http_server.StartHttpServer(Bot, Config); my_http_server.StartHttpServer(Bot, Config);
*/
new HttpAsService(Bot, Config);
} }
if(use_self_keep_alive == true) if(use_self_keep_alive == true)
{ {

View file

@ -16,7 +16,7 @@ namespace BetterSecondBot.DiscordSupervisor
} }
} }
} }
string DiscordBotStatus_laststatus = "";
public string GetStatus() public string GetStatus()
{ {
string reply = ""; string reply = "";

View file

@ -106,7 +106,7 @@ namespace BetterSecondBot.DiscordSupervisor
else else
{ {
controler.Bot.Debug("DiscordBotChatControler - Localchat:" + sender_name + ":" + message); 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 else

View file

@ -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<string> http_args = test.Split('/', StringSplitOptions.RemoveEmptyEntries).ToList();
byte[] data;
resp.StatusCode = 200;
resp.ContentType = "text/html";
if (req.HttpMethod == "GET")
{
KeyValuePair<string, byte[]> 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<string, string> post_args = new Dictionary<string, string>();
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();
}
}
}

View file

@ -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<br/> 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");
}
}
}
}

View file

@ -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");
}
}
}
}

View file

@ -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};
}
}
}

View file

@ -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<ConsoleLogger>();
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<string, tokenInfo> tokens = new Dictionary<string, tokenInfo>();
public void PurgeExpired()
{
List<string> oldTokens = new List<string>();
long now = helpers.UnixTimeNow();
foreach (KeyValuePair<string, tokenInfo> 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;
}
}

View file

@ -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";
}
}
}

View file

@ -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<string> command(string token)
{
if (tokens.Allow(token, getClientIP()) == true)
{
CommandLibCall data = await HttpContext.GetRequestDataAsync<CommandLibCall>();
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; }
}
}

View file

@ -1,4 +0,0 @@
.logo
{
width: 100%;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,201 +0,0 @@
$( document ).ready(function() {
$(".ajaxq").submit(function(e) {
e.preventDefault();
var form = $(this);
var url = form.attr('action');
var method = form.attr('method');
$.ajax({
type: method,
url: url,
data: form.serialize(),
success: function(data)
{
try
{
jsondata = JSON.parse(data);
var redirectdelay = 1500;
if(jsondata.hasOwnProperty('status'))
{
if(jsondata.hasOwnProperty('message'))
{
if(jsondata.status == false)
{
redirectdelay = 3500;
if(jsondata.message != "") alert_warning(jsondata.message);
}
}
if(jsondata.hasOwnProperty('redirect'))
{
if(jsondata.redirect != "")
{
if(jsondata.redirect != null)
{
setTimeout(function(){ $(location).attr('href', jsondata.redirect) }, redirectdelay);
}
}
}
}
else
{
alert_error("Reply from server is not vaild please reload the page and try again.");
}
}
catch(e)
{
alert_warning("Unable to process reply, please reload the page and try again!");
}
},
error: function (data)
{
alert_error(data);
}
});
});
$(".ajax").submit(function(e) {
e.preventDefault();
var form = $(this);
var url = form.attr('action');
var method = form.attr('method');
$.ajax({
type: method,
url: url,
data: form.serialize(),
success: function(data)
{
try
{
jsondata = JSON.parse(data);
var redirectdelay = 1500;
if(jsondata.hasOwnProperty('status'))
{
if(jsondata.hasOwnProperty('message'))
{
if(jsondata.status == true)
{
if(jsondata.message != "") alert_success(jsondata.message);
}
else
{
redirectdelay = 3500;
if(jsondata.message != "") alert_warning(jsondata.message);
}
}
if(jsondata.hasOwnProperty('redirect'))
{
if(jsondata.redirect != "")
{
if(jsondata.redirect != null)
{
setTimeout(function(){ $(location).attr('href', jsondata.redirect) }, redirectdelay);
}
}
}
}
else
{
alert_error("Reply from server is not vaild please reload the page and try again.");
}
}
catch(e)
{
alert_warning("Unable to process reply, please reload the page and try again!");
}
},
error: function (data)
{
alert_error(data);
}
});
});
});
function alert_success(smsg)
{
alert(smsg,"success");
}
function alert_error(smsg)
{
alert(smsg,"error");
}
function alert_warning(smsg)
{
alert(smsg,"warning");
}
function alert(smsg,alerttype)
{
$.notify({
// options
message: smsg
},{
// settings
type: alerttype
});
}
function update_localchat(urlbase)
{
$.ajax({
type: "post",
url: ""+urlbase+"ajax/localchat/get",
success: function(data)
{
try
{
jsondata = JSON.parse(data);
var redirectdelay = 1500;
if(jsondata.hasOwnProperty('status'))
{
if(jsondata.hasOwnProperty('message'))
{
if(jsondata.status == true)
{
$('#localchat').prop('readonly',false);
var text = "";
var addon = "";
$.each( JSON.parse(jsondata.message), function( key, value ) {
text = ""+value+""+addon+""+text+"";
addon = "\n";
});
$('#localchat').val(text);
$('#localchat').prop('readonly',true);
}
}
}
}
catch(e)
{
alert_warning("Unable to process reply, please reload the page and try again!");
}
}
});
}
function update_groupchat(urlbase,groupuuid)
{
$.ajax({
type: "post",
url: ""+urlbase+"ajax/groupchat/get",
data: "groupuuid="+groupuuid+"",
success: function(data)
{
try
{
jsondata = JSON.parse(data);
var redirectdelay = 1500;
if(jsondata.hasOwnProperty('status'))
{
if(jsondata.hasOwnProperty('message'))
{
if(jsondata.status == true)
{
$("localchat").val(jsondata.message);
}
}
}
}
catch(e)
{
alert_warning("Unable to process reply, please reload the page and try again!");
}
}
});
}

View file

@ -1,3 +0,0 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha256-L/W5Wfqfa0sdBNIKN9cG6QA5F2qx4qICmU2VgLruv9Y=" crossorigin="anonymous" />
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/cyborg/bootstrap.min.css" rel="stylesheet" integrity="sha384-l7xaoY0cJM4h9xh1RfazbgJVUZvdtyLWPueWNtLAphf/UbBgOVzqbOTogxPwYLHM" crossorigin="anonymous"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" integrity="sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=" crossorigin="anonymous" />

View file

@ -1,5 +0,0 @@
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.bundle.js" integrity="sha256-Z1t+wxZ7Eh5T1sK6aePWMhEQOghR4jZVLwjwrUZAReE=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-notify@3.1.3/bootstrap-notify.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" integrity="sha256-KzZiKy0DWYsnwMF+X1DvQngQ2/FxF7MF3Ff72XcpuPs=" crossorigin="anonymous"></script>

View file

@ -1,35 +0,0 @@
<html>
<head>
<title>[[html_title]] - [[html_title_after]]</title>
[[html_cs_top_cdn]]
[[html_cs_top]]
<link rel="icon" type="image/png" href="[[url_base]]theme/images/favicon.png">
<link rel="stylesheet" type="text/css" href="[[url_base]]theme/css/site.css">
</head>
<body>
<div class="container">
<div class="row h-75 justify-content-center align-items-center">
<div class="col-6">
<div class="row justify-content-center">
<div class="col-6">
<a href="[[url_base]]"><img class="logo" src="[[url_base]]theme/images/logo.png" alt="logo"/></a>
</div>
</div>
<div class="row">
<div class="col-12">
[[page_content]]
</div>
</div>
</div>
</div>
</div>
[[html_js_bottom_cdn]]
[[html_js_bottom]]
<script type="text/javascript" src="[[url_base]]theme/js/site.js"></script>
<script type="text/javascript">
$( document ).ready(function() {
[[html_js_onready]]
});
</script>
</body>
</html>

View file

@ -1,14 +0,0 @@
<div class="card border border-success rounded">
<div class="card-body">
<h5 class="card-title">Bot webUI auth required</h5>
<form action="[[url_base]]ajax/login/start" method="post" class="ajax">
<div class="row mt-1">
<div class="col-8 offset-2">
<input name="logincode" class="form-control" type="password" placeholder="Please enter your auth code"/></div>
</div>
<div class="row mt-4">
<div class="col-12"><button class="btn btn-primary btn-block" type="submit">Login</button></div>
</div>
</form>
</div>
</div>

View file

@ -1,61 +0,0 @@
<html>
<head>
<title>[[html_title]] - [[html_title_after]]</title>
[[html_cs_top_cdn]]
[[html_cs_top]]
<link rel="icon" type="image/png" href="[[url_base]]theme/images/favicon.png">
<link rel="stylesheet" type="text/css" href="[[url_base]]theme/css/site.css">
</head>
<body>
<div class="container-fluid">
<div class="row mt-1 justify-content-between">
<div class="col-1"><a href="[[url_base]]"><img class="logo" src="[[url_base]]theme/images/logo.png" alt="logo"/></a></div>
</div>
<div class="row">
<div class="col-2">
<div class="row">
<div class="col-12">
<ul class="nav nav-pills flex-column">
[[html_menu]]
</ul>
</div>
</div>
<div class="row align-items-end">
<div class="col-12">
<br/><br/>
<hr/>
</div>
</div>
<div class="row align-items-end">
<div class="col-5">
<a href="[[url_base]]logout"><button type="button" class="btn btn-block btn-secondary btn-sml">Logout</button></a>
</div>
</div>
</div>
<div class="col-10">
<div class="card">
<div class="card-header">
<div class="row mt-1 justify-content-between">
<div class="col-5">[[page_title]]</div>
<div class="col-1 justify-content-end">[[page_actions]]</div>
</div>
</div>
<div class="card-body">
[[page_content]]
</div>
</div>
</div>
</div>
</div>
[[html_js_bottom_cdn]]
[[html_js_bottom]]
<script type="text/javascript" src="[[url_base]]theme/js/site.js"></script>
<script type="text/javascript">
$( document ).ready(function() {
[[html_js_onready]]
});
</script>
</body>
</html>

View file

@ -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<string, long> vaildlogincookies = new Dictionary<string, long>();
public Webui(JsonConfig setconfig, SecondBot setbot)
{
bot = setbot;
siteconfig = setconfig;
}
protected KeyValuePair<bool, bool> Logincookievaild(HttpListenerResponse resp)
{
return Logincookievaild(resp, false);
}
protected KeyValuePair<bool, bool> 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<bool, bool>(true, true);
}
}
}
resp.Cookies.Remove(C);
C.Expired = true;
resp.Cookies.Add(C);
return new KeyValuePair<bool, bool>(true, false);
}
}
return new KeyValuePair<bool, bool>(false, false);
}
public bool Post_process(Json_http_reply reply, HttpListenerResponse resp, List<string> args, Dictionary<string, string> 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<bool, bool> 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<string, string> groupname_uuid = new Dictionary<string, string>();
foreach (KeyValuePair<UUID, Group> 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<string, string> swaps = new Dictionary<string, string>();
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 + " <li class=\"nav-item\"><a href=\"[[url_base]]"+menu_links[loop]+"\" class=\"nav-link " + active+ "\"><i class=\"" + meni_icons[loop] + " text-success\"></i> " + menu_text[loop] + "</a></li>";
loop++;
}
swaps.Add("html_menu", menu);
if (mod == "none")
{
pagecontent = "<p>Please select a menu item on the left</p>";
}
else if(mod == "localchat")
{
pagetitle = "Localchat";
pagecontent = "<textarea readonly cols=\"82\" rows=\"14\" id=\"localchat\" name=\"localchat\"> - Loading local chat please wait -</textarea><br/>" +
"<form method=\"post\" class=\"ajaxq\" action=\"[[url_base]]ajax/localchat/send\">" +
"<input type=\"text\" name=\"message\" id=\"message\" value=\"\" size=\"33\" class=\"form-control\" placeholder=\"Say in localchat\">" +
"<button type\"submit\" class=\"btn btn-primary\">Send</button>"+
"</form>";
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<string,string> 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<string, byte[]> Get_Process(HttpListenerResponse resp, List<string> 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<string, byte[]>("image/png", helpers.ReadResourceFileBinary(Assembly.GetExecutingAssembly(), args[2]));
}
else if (args[1] == "css")
{
return new KeyValuePair<string, byte[]>("text/css", Encoding.UTF8.GetBytes(helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), args[2])));
}
else if (args[1] == "js")
{
return new KeyValuePair<string, byte[]>("text/javascript", Encoding.UTF8.GetBytes(helpers.ReadResourceFile(Assembly.GetExecutingAssembly(), args[2])));
}
}
}
}
}
// get display
KeyValuePair<bool, bool> 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<string, byte[]>("text/html", Encoding.UTF8.GetBytes(reply_with));
}
}
}

View file

@ -1,28 +1,28 @@
<html> <html>
<head> <head>
<title>Secondbot Wiki [[AREA]]</title> <title>Secondbot Wiki [[AREA]]</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous"> <link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous">
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-3"> <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-3">
<div class="container-fluid"> <div class="container-fluid">
<a href="[[RETURNROOT]]index.html" class="navbar-brand mr-3">SecondBot Wiki</a> <a href="[[RETURNROOT]]index.html" class="navbar-brand mr-3">SecondBot Wiki</a>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse"> <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav"> <div class="navbar-nav">
<a href="[[SUBFOLDER]]Core.html" class="nav-item nav-link [[CoreActive]]">Main commands [Core]</a> <a href="[[SUBFOLDER]]Core.html" class="nav-item nav-link [[CoreActive]]">Main commands [Core]</a>
<a href="[[SUBFOLDER]]JSON.html" class="nav-item nav-link [[JSONActive]]">JSON [Bot Config file]</a> <a href="[[SUBFOLDER]]JSON.html" class="nav-item nav-link [[JSONActive]]">JSON [Bot Config file]</a>
<a href="[[SUBFOLDER]]RLVapi.html" class="nav-item nav-link [[RLVapiActive]]">Restrained Love API [RLV]</a> <a href="[[SUBFOLDER]]RLVapi.html" class="nav-item nav-link [[RLVapiActive]]">Restrained Love API [RLV]</a>
<a href="[[SUBFOLDER]]HTTPpost.html" class="nav-item nav-link [[HTTPpostActive]]">Post [HTTP]</a> <a href="[[SUBFOLDER]]HTTP.html" class="nav-item nav-link [[HTTPActive]]">WebLink [HTTP]</a>
</div> </div>
<div class="navbar-nav ml-auto"> <div class="navbar-nav ml-auto">
<a href="https://bitbucket.org/streamadmin/secondbot/src/master/" target="_BLANK" class="nav-item nav-link">Get Source</a> <a href="https://bitbucket.org/streamadmin/secondbot/src/master/" target="_BLANK" class="nav-item nav-link">Get Source</a>
<a href="secondlife:///app/group/cbda0f35-1c0b-2a45-e693-62a63a8f9d88/about" target="_BLANK" class="nav-item nav-link">SL group</a> <a href="secondlife:///app/group/cbda0f35-1c0b-2a45-e693-62a63a8f9d88/about" target="_BLANK" class="nav-item nav-link">SL group</a>
</div> </div>
</div> </div>
</div> </div>
</nav> </nav>
<div class="container-fluid"> <div class="container-fluid">

View file

@ -17,7 +17,7 @@ namespace BetterSecondBot.WikiMake
protected string html_footer = ""; protected string html_footer = "";
protected string buildVersion = ""; protected string buildVersion = "";
protected SimpleIO io; protected SimpleIO io;
protected Dictionary<string,string> seen_command_names = new Dictionary<string, string>(); protected Dictionary<string, string> seen_command_names = new Dictionary<string, string>();
public string CreateRoot() public string CreateRoot()
@ -28,7 +28,7 @@ namespace BetterSecondBot.WikiMake
sb.Append(buildVersion); sb.Append(buildVersion);
sb.Append("</h3><br/>"); sb.Append("</h3><br/>");
sb.Append("<table class='table table-striped table-bordered'><thead><tr><th>Interface</th><th>Commands</th><th>Status</th></tr></thead><tbody>"); sb.Append("<table class='table table-striped table-bordered'><thead><tr><th>Interface</th><th>Commands</th><th>Status</th></tr></thead><tbody>");
foreach (KeyValuePair<string,KeyValuePair<int, string>> entry in api_reports) foreach (KeyValuePair<string, KeyValuePair<int, string>> entry in api_reports)
{ {
sb.Append("<tr>"); sb.Append("<tr>");
sb.Append("<td><a href='[[SUBFOLDER]]"); sb.Append("<td><a href='[[SUBFOLDER]]");
@ -51,7 +51,7 @@ namespace BetterSecondBot.WikiMake
return sb.ToString(); return sb.ToString();
} }
protected static StringBuilder MenuActive(StringBuilder index,string area) protected static StringBuilder MenuActive(StringBuilder index, string area)
{ {
Dictionary<string, string> active_swaps = new Dictionary<string, string> Dictionary<string, string> active_swaps = new Dictionary<string, string>
{ {
@ -59,18 +59,17 @@ namespace BetterSecondBot.WikiMake
{ "CLIActive", "" }, { "CLIActive", "" },
{ "JSONActive", "" }, { "JSONActive", "" },
{ "RLVapiActive", "" }, { "RLVapiActive", "" },
{ "HTTPgetActive", "" }, { "HTTPActive", "" }
{ "HTTPpostActive", "" }
}; };
active_swaps["" + area + "Active"] = "Active"; active_swaps["" + area + "Active"] = "Active";
foreach(KeyValuePair<string,string> pair in active_swaps) foreach (KeyValuePair<string, string> pair in active_swaps)
{ {
index = index.Replace("[["+ pair.Key+"]]", pair.Value); index = index.Replace("[[" + pair.Key + "]]", pair.Value);
} }
return index; return index;
} }
protected void InterfaceCommands(string area, API_supported_interface shared_interface,bool track_commands=false) protected void InterfaceCommands(string area, API_supported_interface shared_interface, bool track_commands = false)
{ {
string[] cmds = shared_interface.GetCommandsList(); string[] cmds = shared_interface.GetCommandsList();
string interface_name = shared_interface.GetType().Name; string interface_name = shared_interface.GetType().Name;
@ -84,7 +83,7 @@ namespace BetterSecondBot.WikiMake
} }
else else
{ {
LogFormater.Debug("command " + c + " from " + interface_name + " overlaps an ready loaded command from "+ seen_command_names[c]+""); LogFormater.Debug("command " + c + " from " + interface_name + " overlaps an ready loaded command from " + seen_command_names[c] + "");
} }
} }
string workspace = shared_interface.GetCommandWorkspace(c); string workspace = shared_interface.GetCommandWorkspace(c);
@ -94,7 +93,7 @@ namespace BetterSecondBot.WikiMake
sb.Append(buildVersion); sb.Append(buildVersion);
sb.Append("</h3><br/>"); sb.Append("</h3><br/>");
sb.Append("<h4>Interface: <a href='[[AREA]].html'>[[AREA]]</a>"); sb.Append("<h4>Interface: <a href='[[AREA]].html'>[[AREA]]</a>");
if(workspace != "") if (workspace != "")
{ {
sb.Append(" / <a href='[[AREA]][[WORKSPACE]].html'>[[WORKSPACE]]</a>"); sb.Append(" / <a href='[[AREA]][[WORKSPACE]].html'>[[WORKSPACE]]</a>");
} }
@ -103,7 +102,7 @@ namespace BetterSecondBot.WikiMake
int minargs = shared_interface.GetCommandArgs(c); int minargs = shared_interface.GetCommandArgs(c);
string[] arg_types = shared_interface.GetCommandArgTypes(c); string[] arg_types = shared_interface.GetCommandArgTypes(c);
string[] arg_hints = shared_interface.GetCommandArgHints(c); string[] arg_hints = shared_interface.GetCommandArgHints(c);
if(area == "Core") if (area == "Core")
{ {
StringBuilder ExampleCall = new StringBuilder(); StringBuilder ExampleCall = new StringBuilder();
ExampleCall.Append("Example: [[COMMAND]]"); ExampleCall.Append("Example: [[COMMAND]]");
@ -194,9 +193,9 @@ namespace BetterSecondBot.WikiMake
sb.Append(buildVersion); sb.Append(buildVersion);
sb.Append("</h4><br/>"); sb.Append("</h4><br/>");
sb.Append("<table class='datatable table table-striped table-bordered'><thead><tr><th>Command</th><th>Min args</th></tr></thead><tbody>"); sb.Append("<table class='datatable table table-striped table-bordered'><thead><tr><th>Command</th><th>Min args</th></tr></thead><tbody>");
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); int args = shared_interface.GetCommandArgs(c);
sb.Append("<tr><td><a href='[[AREA]]"); sb.Append("<tr><td><a href='[[AREA]]");
@ -257,9 +256,9 @@ namespace BetterSecondBot.WikiMake
sb.Replace("[[AREA]]", area); sb.Replace("[[AREA]]", area);
sb.Append(html_footer); sb.Append(html_footer);
sb = DebugModeCreateWiki.MenuActive(sb, area); sb = DebugModeCreateWiki.MenuActive(sb, area);
io.writefile(""+ area+".html", sb.ToString()); io.writefile("" + area + ".html", sb.ToString());
} }
protected void InterfaceWiki(string area,API_supported_interface shared_interface, bool track_commands = false) protected void InterfaceWiki(string area, API_supported_interface shared_interface, bool track_commands = false)
{ {
LogFormater.Info("[WIKI] Starting area " + area + ""); LogFormater.Info("[WIKI] Starting area " + area + "");
// create index // create index
@ -289,21 +288,494 @@ namespace BetterSecondBot.WikiMake
// CMD // CMD
BSB.Commands.CoreCommandsInterface cmd = new BSB.Commands.CoreCommandsInterface(null); BSB.Commands.CoreCommandsInterface cmd = new BSB.Commands.CoreCommandsInterface(null);
api_reports.Add("Core", new KeyValuePair<int, string>(cmd.ApiCommandsCount, "Basic")); api_reports.Add("Core", new KeyValuePair<int, string>(cmd.ApiCommandsCount, "Good"));
InterfaceWiki("Core", cmd,true); InterfaceWiki("Core", cmd, true);
// RLVapi // RLVapi
BSB.RLV.RLVcontrol RLVapi = new BSB.RLV.RLVcontrol(null); BSB.RLV.RLVcontrol RLVapi = new BSB.RLV.RLVcontrol(null);
api_reports.Add("RLVapi", new KeyValuePair<int, string>(RLVapi.ApiCommandsCount, "Basic")); api_reports.Add("RLVapi", new KeyValuePair<int, string>(RLVapi.ApiCommandsCount, "Limited"));
InterfaceWiki("RLVapi", RLVapi, true); InterfaceWiki("RLVapi", RLVapi, true);
// HTTP post // HTTP
BetterSecondBot.HttpServer.HTTPCommandsInterface http_post = new BetterSecondBot.HttpServer.HTTPCommandsInterface(null,null); HTTPWiki();
api_reports.Add("HTTPpost", new KeyValuePair<int, string>(http_post.ApiCommandsCount, "Limited"));
InterfaceWiki("HTTPpost", http_post);
io.ChangeRoot("wiki"); io.ChangeRoot("wiki");
io.writefile("index.html", CreateRoot()); io.writefile("index.html", CreateRoot());
} }
}
protected void HTTPWiki()
{
LogFormater.Info("[WIKI] Starting area HTTP endpoint");
HTTPendpoint HTTP = new HTTPendpoint();
HTTPmenu("HTTP", HTTP);
// create workspaces
HTTPWorkspaces("HTTP", HTTP);
// create commands
HTTPCommands("HTTP", HTTP);
HTTP = null;
LogFormater.Info("[WIKI] Done with area");
}
protected string getURLargs(Dictionary<string, KeyValuePair<string, string>> values)
{
List<string> urlargs = new List<string>();
foreach (KeyValuePair<string, KeyValuePair<string, string>> entry in values)
{
if (entry.Value.Key == "URLARG")
{
urlargs.Add("{"+entry.Key+"}");
}
}
return String.Join('/', urlargs);
}
protected void HTTPCommands(string area, HTTPendpoint HTTP)
{
foreach (string workspace in HTTP.getEndPoints())
{
foreach (string c in HTTP.getEndpointCommands(workspace))
{
StringBuilder sb = new StringBuilder();
sb.Append(html_header);
sb.Append("<h3>Build: ");
sb.Append(buildVersion);
sb.Append("</h3><br/>");
sb.Append("<h4>Interface: <a href='[[AREA]].html'>[[AREA]]</a>");
if (workspace != "")
{
sb.Append(" / <a href='[[AREA]][[WORKSPACE]].html'>[[WORKSPACE]]</a>");
}
sb.Append("</h4><hr/><h3>[[COMMAND]]</h3>");
sb.Append("[[URLENDPOINT]]/[[WORKSPACE]]/[[COMMAND]]/[[URLADDON]]<br/>");
sb.Append("Method: [[APIMETHOD]]");
sb.Append("<hr style='border-top: 1px dashed #dcdcdc;'>");
sb.Append("[[HELP]]");
Dictionary<string, KeyValuePair<string, string>> values = HTTP.getCommandArgs(workspace, c);
int ValueCount = values.Count;
if (ValueCount > 0)
{
sb.Append("<hr/><h4>Args helper</h4>");
sb.Append("<table class='table table-striped table-bordered'><thead><tr><td>Name</td><th>Type</th><th>Hint</th></tr></thead><tbody>");
foreach (KeyValuePair<string, KeyValuePair<string, string>> 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("<tr><td>" + entry.Key + "</td><td>" + type + "</td><td>" + hint + "</td></tr>");
}
sb.Append("</tbody></table>");
}
string[] returnvalues = HTTP.getReturnsValues(workspace, c);
if (returnvalues.Length > 0)
{
sb.Append("<hr/><h4>Possible replys</h4>");
sb.Append("<ul>");
foreach (string entry in returnvalues)
{
sb.Append("<li>"+entry+"</li>");
}
sb.Append("</ul>");
}
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<int, string>(HTTP.getFullCount(), "Basic"));
// create index
StringBuilder sb = new StringBuilder();
sb.Append(html_header);
sb.Append("<h3>Interface:");
sb.Append("HTTP");
sb.Append("</h3><br/>");
sb.Append("<h4>Build: ");
sb.Append(buildVersion);
sb.Append("</h4><br/>");
sb.Append("<table class='datatable table table-striped table-bordered'><thead><tr><th>Workspace</th><th>Commands</th></tr></thead><tbody>");
foreach (string workspace in HTTP.getEndPoints())
{
string workspace_link = "<a href='[[AREA]]" + workspace + ".html'>" + workspace + "</a>";
sb.Append("<tr><td>");
sb.Append(workspace_link);
sb.Append("</td><td>");
sb.Append(HTTP.getEndpointCount(workspace).ToString());
sb.Append("</td></tr>");
}
sb.Append("</tbody></table>");
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("<h3>Interface: <a href='[[AREA]].html'>[[AREA]]</a>");
if (workspace != "")
{
sb.Append(" / [[WORKSPACE]]");
}
sb.Append("</h3><br/>");
sb.Append("<h4>Build: ");
sb.Append(buildVersion);
sb.Append("</h4><br/>");
sb.Append("<table class='datatable table table-striped table-bordered'><thead><tr><th>Command</th><th>Min args</th></tr></thead><tbody>");
foreach (string c in HTTP.getEndpointCommands(workspace))
{
int args = HTTP.getCommandArgCount(workspace, c);
sb.Append("<tr><td><a href='[[AREA]]");
sb.Append(workspace);
sb.Append(c);
sb.Append(".html'>");
sb.Append(c);
sb.Append("</a></td><td>");
sb.Append(args.ToString());
sb.Append("</td></tr>");
}
sb.Append("</tbody></table>");
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<Endpoint> endpoints = new List<Endpoint>();
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<string> commands = new List<string>();
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<string> names = new List<string>();
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<string, KeyValuePair<string, string>> getCommandArgs(string endpoint, string command)
{
Dictionary<string, KeyValuePair<string, string>> empty = new Dictionary<string, KeyValuePair<string, string>>();
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<APIcall> callable = new List<APIcall>();
}
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<string, KeyValuePair<string, string>> values = new Dictionary<string, KeyValuePair<string, string>>();
public List<string> returns = new List<string>();
public bool RequiresToken = true;
public APIcall()
{
Setup();
}
public virtual void Setup()
{
if (RequiresToken == true)
{
returns.Add("Token not accepted");
values.Add(
"token",
new KeyValuePair<string, string>(
"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) <br/>to use with all other requests";
values.Add("Authcode", new KeyValuePair<string, string>("string", "the first 10 chars of SHA1(unixtime+WebUIkey)<br/>unixtime can be +- 30 of the bots time."));
returns = new List<string>();
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<string, string>(
"JsonObject",
"A JSON object formated as follows<br/>Command: string<br/>Args: string[]<br/>AuthCode: string<br/>========<br/>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<br/>Formated as follows<br/>InventoryMapItem<br/><ul><li>id: UUID</li><li>name: String</li><li>typename: String</li></ul>";
values.Add(
"folderUUID",
new KeyValuePair<string, string>(
"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<br/>Formated as follows<br/>InventoryMapItem<br/><ul><li>id: UUID</li><li>name: String</li><li>subfolders: InventoryMapFolder[]</li></ul>";
returns.Add("array of InventoryMapFolder");
base.Setup();
}
}
} }

View file

@ -21,8 +21,8 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier> <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup> <ItemGroup> </PropertyGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="2.3.0-dev-20201219.2" /> <PackageReference Include="Discord.Net" Version="2.3.0" />
<PackageReference Include="Discord.Net.WebSocket" Version="2.3.0-dev-20201219.2" /> <PackageReference Include="Discord.Net.WebSocket" Version="2.3.0" />
<PackageReference Include="RestSharp" Version="106.11.2" /> <PackageReference Include="RestSharp" Version="106.11.2" />
</ItemGroup> </ItemGroup>