From 186263c20e441ead2d03deac54bdbe7fd30a9636 Mon Sep 17 00:00:00 2001 From: Madpeterz <57852827+Madpeterz@users.noreply.github.com> Date: Mon, 15 Feb 2021 06:12:43 +0000 Subject: [PATCH] New stuff,Fixs and API changes Fixed wiki make I hope HTTP api "ParcelEstate" split into Parcel, Estate ready for more later Datastorage event class for stuff (currently only Estate bans) Class renamed from CliExitOnLogout to Cli. Events moved from athome to Events, and the old Athome renamed attachable --- BetterSecondbot/BetterAtHome/BetterAtHome.cs | 4 +- .../BetterRelayService/BetterRelay.cs | 4 +- .../{CliExitOnLogout.cs => Cli.cs} | 36 +++--- BetterSecondbot/DataStorage/Datastorage.cs | 80 +++++++++++++ .../DiscordSupervisor/Discord-super.cs | 4 +- BetterSecondbot/DiscordSupervisor/Values.cs | 2 +- BetterSecondbot/HttpService/Estate.cs | 32 +++++ BetterSecondbot/HttpService/HttpAsService.cs | 7 +- .../{ParcelEstate.cs => Parcel.cs} | 8 +- BetterSecondbot/WikiMake/wikimake.cs | 8 +- Core/bottypes/AVstorageBot.cs | 2 +- Core/bottypes/AttachableBot.cs | 66 +++++++++++ Core/bottypes/EventsBot.cs | 55 +++++++++ Core/bottypes/athome.cs | 109 ------------------ Core/bottypes/loadorder.txt | 2 +- 15 files changed, 274 insertions(+), 145 deletions(-) rename BetterSecondbot/{CliExitOnLogout.cs => Cli.cs} (72%) create mode 100644 BetterSecondbot/DataStorage/Datastorage.cs create mode 100644 BetterSecondbot/HttpService/Estate.cs rename BetterSecondbot/HttpService/{ParcelEstate.cs => Parcel.cs} (83%) create mode 100644 Core/bottypes/AttachableBot.cs delete mode 100644 Core/bottypes/athome.cs diff --git a/BetterSecondbot/BetterAtHome/BetterAtHome.cs b/BetterSecondbot/BetterAtHome/BetterAtHome.cs index 9f02bce..d7d1492 100644 --- a/BetterSecondbot/BetterAtHome/BetterAtHome.cs +++ b/BetterSecondbot/BetterAtHome/BetterAtHome.cs @@ -9,7 +9,7 @@ namespace BetterSecondbot.BetterAtHome { public class BetterAtHome { - protected CliExitOnLogout controler = null; + protected Cli controler = null; protected JsonConfig configfile; protected bool LoginFailed = false; @@ -21,7 +21,7 @@ namespace BetterSecondbot.BetterAtHome protected long LastTeleportEvent = 0; protected List homeRegions = new List(); - public BetterAtHome(CliExitOnLogout setcontroler, JsonConfig configfile) + public BetterAtHome(Cli setcontroler, JsonConfig configfile) { controler = setcontroler; this.configfile = configfile; diff --git a/BetterSecondbot/BetterRelayService/BetterRelay.cs b/BetterSecondbot/BetterRelayService/BetterRelay.cs index a0b2488..c3cb66e 100644 --- a/BetterSecondbot/BetterRelayService/BetterRelay.cs +++ b/BetterSecondbot/BetterRelayService/BetterRelay.cs @@ -16,7 +16,7 @@ namespace BetterSecondBot.BetterRelayService public class BetterRelay { Discord_super superV = null; - CliExitOnLogout controler = null; + Cli controler = null; List DiscordRelay = new List(); List LocalchatRelay = new List(); @@ -24,7 +24,7 @@ namespace BetterSecondBot.BetterRelayService List AvatarIMRelay = new List(); List GroupChatRelay = new List(); - public BetterRelay(CliExitOnLogout setcontroler, Discord_super setdiscord, bool running_in_docker) + public BetterRelay(Cli setcontroler, Discord_super setdiscord, bool running_in_docker) { controler = setcontroler; superV = setdiscord; diff --git a/BetterSecondbot/CliExitOnLogout.cs b/BetterSecondbot/Cli.cs similarity index 72% rename from BetterSecondbot/CliExitOnLogout.cs rename to BetterSecondbot/Cli.cs index 5061b37..e91325a 100644 --- a/BetterSecondbot/CliExitOnLogout.cs +++ b/BetterSecondbot/Cli.cs @@ -6,15 +6,17 @@ using BetterSecondBot.bottypes; using System.Threading; using static BetterSecondBot.Program; using BetterSecondbot.BetterAtHome; +using BetterSecondbot.DataStorage; namespace BetterSecondBot { - public class CliExitOnLogout + public class Cli { public bool Exited { get { return exitBot; } } protected bool exitBot = false; public SecondBot Bot; protected BetterAtHome betterAtHomeService; + protected Datastorage datastorage; protected void keep_alive() { @@ -30,35 +32,37 @@ namespace BetterSecondBot Bot.Log2File(LogFormater.Status(Bot.LastStatusMessage, false), ConsoleLogLogLevel.Status); } } - Thread.Sleep(1000); + Thread.Sleep(1500); } betterAtHomeService = null; } - public CliExitOnLogout(JsonConfig Config, bool as_docker, bool use_self_keep_alive) + + public void attachEventListenerObjects(JsonConfig Config, bool as_docker) + { + if (Config.Http_Enable == true) + { + new Thread(() => + { + new HttpAsService(Bot, Config, as_docker); + }).Start(); + } + betterAtHomeService = new BetterAtHome(this, Config); + datastorage = new Datastorage(this); + } + public Cli(JsonConfig Config, bool as_docker, bool use_self_keep_alive) { exitBot = false; - LogFormater.Status("Mode: Cli [Basic]"); if (helpers.botRequired(Config) == true) { Bot = new SecondBot(); Bot.Setup(Config, AssemblyInfo.GetGitHash()); - betterAtHomeService = new BetterAtHome(this, Config); + if (as_docker == true) { Bot.AsDocker(); } Bot.Start(); - if (Config.Http_Enable == true) - { - /* - SecondBotHttpServer my_http_server = new SecondBotHttpServer(); - my_http_server.StartHttpServer(Bot, Config); - */ - new Thread(() => - { - new HttpAsService(Bot, Config, as_docker); - }).Start(); - } + attachEventListenerObjects(Config, as_docker); if(use_self_keep_alive == true) { keep_alive(); diff --git a/BetterSecondbot/DataStorage/Datastorage.cs b/BetterSecondbot/DataStorage/Datastorage.cs new file mode 100644 index 0000000..2effc6d --- /dev/null +++ b/BetterSecondbot/DataStorage/Datastorage.cs @@ -0,0 +1,80 @@ +using BetterSecondBot; +using BetterSecondBotShared.Static; +using OpenMetaverse; +using BetterSecondBot.bottypes; + +namespace BetterSecondbot.DataStorage +{ + public class Datastorage + { + protected Cli controler = null; + + public Datastorage(Cli setcontroler) + { + controler = setcontroler; + attach_events(); + } + + protected void attach_events() + { + controler.Bot.GetClient.Estate.EstateBansReply += EstateBansReply; + controler.Bot.ChangeSimEvent += ChangedSim; + controler.Bot.LoginProgess += LoginProcess; + controler.Bot.StatusMessageEvent += StatusPing; + } + + protected void StatusPing(object o, StatusMessageEvent e) + { + if(simname != "") + { + if (controler.Bot.GetClient.Network.CurrentSim.IsEstateManager == true) + { + long dif = helpers.UnixTimeNow() - controler.Bot._lastUpdatedSimBlacklist.lastupdated; + if(dif > 120) + { + controler.Bot.GetClient.Estate.RequestInfo(); + } + } + } + } + + protected void LoginProcess(object o, LoginProgressEventArgs e) + { + simname = ""; + if (e.Status == LoginStatus.Success) + { + ChangedSim(null, null); + } + } + + protected string simname = ""; + protected void ChangedSim(object o, SimChangedEventArgs e) + { + simname = ""; + if (controler.Bot.GetClient.Network.CurrentSim != null) + { + simname = controler.Bot.GetClient.Network.CurrentSim.Name; + } + if(simname != "") + { + if(controler.Bot.GetClient.Network.CurrentSim.IsEstateManager == true) + { + controler.Bot.GetClient.Estate.RequestInfo(); + } + } + } + + protected void EstateBansReply(object o, EstateBansReplyEventArgs e) + { + simBlacklistDataset working = new simBlacklistDataset(); + working.lastupdated = helpers.UnixTimeNow(); + foreach(UUID av in e.Banned) + { + working.banned.Add(av,controler.Bot.FindAvatarKey2Name(av)); + } + working.regionname = controler.Bot.GetClient.Network.CurrentSim.Name; + controler.Bot.UpdateSimBlacklist(working); + } + + } +} diff --git a/BetterSecondbot/DiscordSupervisor/Discord-super.cs b/BetterSecondbot/DiscordSupervisor/Discord-super.cs index 4f67f73..fe6279d 100644 --- a/BetterSecondbot/DiscordSupervisor/Discord-super.cs +++ b/BetterSecondbot/DiscordSupervisor/Discord-super.cs @@ -46,7 +46,7 @@ namespace BetterSecondBot.DiscordSupervisor } } } - Thread.Sleep(1000); + Thread.Sleep(1500); } } public Discord_super(JsonConfig configfile, bool as_docker) @@ -82,7 +82,7 @@ namespace BetterSecondBot.DiscordSupervisor controler.Bot.KillMePlease(); controler = null; } - controler = new CliExitOnLogout(myconfig, running_as_docker, self_keep_alive); + controler = new Cli(myconfig, running_as_docker, self_keep_alive); controler.Bot.setDiscordClient(DiscordClient); relayService = new BetterRelay(controler, this, running_as_docker); attach_events(); diff --git a/BetterSecondbot/DiscordSupervisor/Values.cs b/BetterSecondbot/DiscordSupervisor/Values.cs index 08ee72c..090a672 100644 --- a/BetterSecondbot/DiscordSupervisor/Values.cs +++ b/BetterSecondbot/DiscordSupervisor/Values.cs @@ -9,7 +9,7 @@ namespace BetterSecondBot.DiscordSupervisor { protected JsonConfig myconfig; protected bool exit_super = false; - protected CliExitOnLogout controler; + protected Cli controler; protected bool running_as_docker = false; protected bool discord_group_relay; diff --git a/BetterSecondbot/HttpService/Estate.cs b/BetterSecondbot/HttpService/Estate.cs new file mode 100644 index 0000000..a9a9556 --- /dev/null +++ b/BetterSecondbot/HttpService/Estate.cs @@ -0,0 +1,32 @@ +using BetterSecondBotShared.Json; +using BetterSecondBot.bottypes; +using EmbedIO; +using EmbedIO.Routing; +using System; +using Newtonsoft.Json; + +namespace BetterSecondBot.HttpService +{ + + + + public class HttpAPIEstate : WebApiControllerWithTokens + { + public HttpAPIEstate(SecondBot mainbot, TokenStorage setuptokens) : base(mainbot, setuptokens) + { + + } + + [About("Requests the estate banlist")] + [ReturnHints("")] + [Route(HttpVerbs.Get, "/banlist/{token}")] + public Object Banlist(string token) + { + if (tokens.Allow(token, "estate", "banlist", getClientIP()) == true) + { + return BasicReply(JsonConvert.SerializeObject(bot._lastUpdatedSimBlacklist)); + } + return BasicReply("Token not accepted"); + } + } +} diff --git a/BetterSecondbot/HttpService/HttpAsService.cs b/BetterSecondbot/HttpService/HttpAsService.cs index c1d62e1..a09fd92 100644 --- a/BetterSecondbot/HttpService/HttpAsService.cs +++ b/BetterSecondbot/HttpService/HttpAsService.cs @@ -189,8 +189,11 @@ namespace BetterSecondBot.HttpService .WithWebApi("/core", m => m .WithController(() => new HttpApiCore(Config, Bot, Tokens)) ) - .WithWebApi("/parcelestate", m => m - .WithController(() => new HttpApiParcelEstate(Bot,Tokens)) + .WithWebApi("/parcel", m => m + .WithController(() => new HttpApiParcel(Bot,Tokens)) + ) + .WithWebApi("/estate", m => m + .WithController(() => new HttpAPIEstate(Bot, Tokens)) ) .WithModule(new ActionModule("/", HttpVerbs.Any, ctx => ctx.SendDataAsync(new { Message = "Error" }))); return server; diff --git a/BetterSecondbot/HttpService/ParcelEstate.cs b/BetterSecondbot/HttpService/Parcel.cs similarity index 83% rename from BetterSecondbot/HttpService/ParcelEstate.cs rename to BetterSecondbot/HttpService/Parcel.cs index 3e47767..0145e01 100644 --- a/BetterSecondbot/HttpService/ParcelEstate.cs +++ b/BetterSecondbot/HttpService/Parcel.cs @@ -12,18 +12,18 @@ using static OpenMetaverse.ParcelManager; namespace BetterSecondBot.HttpService { - class HttpApiParcelEstate: WebApiControllerWithTokens + class HttpApiParcel : WebApiControllerWithTokens { - public HttpApiParcelEstate(SecondBot mainbot, TokenStorage setuptokens) : base(mainbot, setuptokens) { } + public HttpApiParcel(SecondBot mainbot, TokenStorage setuptokens) : base(mainbot, setuptokens) { } [About("Fetchs the parcel ban list of the parcel the bot is currently on
If the name returned is lookup the bot is currently requesting the avatar name")] [ReturnHints("array of UUID=Name")] [ReturnHints("Error not in a sim")] [ReturnHints("Parcel data not ready")] - [Route(HttpVerbs.Get, "/parcelbanlist/{token}")] + [Route(HttpVerbs.Get, "/banlist/{token}")] public object parcelbanlist(string token) { - if (tokens.Allow(token, "parcelestate", "parcelbanlist", getClientIP()) == true) + if (tokens.Allow(token, "parcel", "banlist", getClientIP()) == true) { if (bot.GetClient.Network.CurrentSim != null) { diff --git a/BetterSecondbot/WikiMake/wikimake.cs b/BetterSecondbot/WikiMake/wikimake.cs index 69b3200..d2ea227 100644 --- a/BetterSecondbot/WikiMake/wikimake.cs +++ b/BetterSecondbot/WikiMake/wikimake.cs @@ -174,7 +174,7 @@ namespace BetterSecondBot.WikiMake sb.Replace("[[SUBFOLDER]]", ""); sb.Replace("[[RETURNROOT]]", "../"); sb = DebugModeCreateWiki.MenuActive(sb, area); - string target_file = "" + area + "" + workspace + "" + c.ToLowerInvariant() + ".html"; + string target_file = "" + area + "" + workspace + "" + c + ".html"; io.writefile(target_file, sb.ToString()); } } @@ -392,9 +392,6 @@ namespace BetterSecondBot.WikiMake } sb.Append(""); } - - - sb = DebugModeCreateWiki.MenuActive(sb, area); sb.Append(html_footer); sb.Replace("[[COMMAND]]", c); @@ -566,7 +563,8 @@ namespace BetterSecondBot.WikiMake createEndpoint("im", typeof(HttpApiIM)); createEndpoint("groups", typeof(HttpApiGroup)); createEndpoint("chat", typeof(HttpApiLocalchat)); - createEndpoint("parcelestate", typeof(HttpApiParcelEstate)); + createEndpoint("parcel", typeof(HttpApiParcel)); + createEndpoint("estate", typeof(HttpAPIEstate)); } public string getCommandMethod(string endpoint, string command) diff --git a/Core/bottypes/AVstorageBot.cs b/Core/bottypes/AVstorageBot.cs index b3ac2b2..b3aadab 100644 --- a/Core/bottypes/AVstorageBot.cs +++ b/Core/bottypes/AVstorageBot.cs @@ -9,7 +9,7 @@ using System.Text; namespace BetterSecondBot.bottypes { - public abstract class AVstorageBot : AtHome + public abstract class AVstorageBot : AttachableBot { protected UUID master_uuid = UUID.Zero; public UUID getMaster_uuid { get { return master_uuid; } } diff --git a/Core/bottypes/AttachableBot.cs b/Core/bottypes/AttachableBot.cs new file mode 100644 index 0000000..a8f5d6f --- /dev/null +++ b/Core/bottypes/AttachableBot.cs @@ -0,0 +1,66 @@ +using System; +using OpenMetaverse; +using BetterSecondBotShared.Static; +using System.Collections.Generic; + +namespace BetterSecondBot.bottypes +{ + public class NextHomeRegionArgs: EventArgs + { + } + + public abstract class AttachableBot : MessageSwitcherBot + { + protected string BetterAtHomeAction = ""; + + protected simBlacklistDataset lastUpdatedSimBlacklist = new simBlacklistDataset(); + public simBlacklistDataset _lastUpdatedSimBlacklist { get { return lastUpdatedSimBlacklist; } } + + public void UpdateSimBlacklist(simBlacklistDataset current) + { + lastUpdatedSimBlacklist = current; + } + + public void ResetAtHome() + { + + } + + public void SetBetterAtHomeAction(string BetterAtHomeAction) + { + this.BetterAtHomeAction = BetterAtHomeAction; + } + + public override string GetStatus() + { + return base.GetStatus() + " <@BetterAtHome: " + BetterAtHomeAction + ">"; + } + + public string TeleportWithSLurl(string sl_url) + { + string[] bits = helpers.ParseSLurl(sl_url); + if (helpers.notempty(bits) == true) + { + if (bits.Length == 4) + { + float.TryParse(bits[1], out float posX); + float.TryParse(bits[2], out float posY); + float.TryParse(bits[3], out float posZ); + string regionName = bits[0]; + Client.Self.Teleport(regionName, new Vector3(posX, posY, posZ)); + return "ok"; + } + return "Invaild bits length for SLurl"; + } + return "No bits decoded"; + } + } + + + public class simBlacklistDataset + { + public long lastupdated = 0; + public Dictionary banned = new Dictionary(); + public string regionname = ""; + } +} diff --git a/Core/bottypes/EventsBot.cs b/Core/bottypes/EventsBot.cs index d0b7ff3..62668a4 100644 --- a/Core/bottypes/EventsBot.cs +++ b/Core/bottypes/EventsBot.cs @@ -119,17 +119,46 @@ namespace BetterSecondBot.bottypes public class EventsBot : VoidEventBot { + private EventHandler _ChangeSim; + private EventHandler _AlertMessage; + private EventHandler _LoginProgess; + private EventHandler _NextHomeRegion; private EventHandler _MessageEvent; private EventHandler _StatusMessageEvent; private EventHandler _GroupsEvent; private EventHandler _SendImEvent; + private readonly object _ChangeSimLock = new object(); + private readonly object _AlertMessageLock = new object(); + private readonly object _LoginProgressLock = new object(); + private readonly object _NextHomeRegionLock = new object(); private readonly object _MessageEventLock = new object(); private readonly object _StatusMessageEventlock = new object(); private readonly object _GroupsEventLock = new object(); private readonly object _SendImEventLock = new object(); + public event EventHandler ChangeSimEvent + { + add { lock (_ChangeSimLock) { _ChangeSim += value; } } + remove { lock (_ChangeSimLock) { _ChangeSim -= value; } } + } + public event EventHandler AlertMessage + { + add { lock (_AlertMessageLock) { _AlertMessage += value; } } + remove { lock (_AlertMessageLock) { _AlertMessage -= value; } } + } + public event EventHandler LoginProgess + { + add { lock (_LoginProgressLock) { _LoginProgess += value; } } + remove { lock (_LoginProgressLock) { _LoginProgess -= value; } } + } + public event EventHandler NextHomeRegion + { + add { lock (_NextHomeRegionLock) { _NextHomeRegion += value; } } + remove { lock (_NextHomeRegionLock) { _NextHomeRegion -= value; } } + } + public event EventHandler MessageEvent { add { lock (_MessageEventLock) { _MessageEvent += value; } } @@ -187,6 +216,32 @@ namespace BetterSecondBot.bottypes } } + protected override void LoginHandler(object o, LoginProgressEventArgs e) + { + base.LoginHandler(o, e); + EventHandler handler = _LoginProgess; + handler?.Invoke(this, e); + } + + protected void ChangeSim(object sender, SimChangedEventArgs e) + { + EventHandler handler = _ChangeSim; + handler?.Invoke(this, e); + } + + protected void AlertEvent(object sender, AlertMessageEventArgs e) + { + EventHandler handler = _AlertMessage; + handler?.Invoke(this, e); + } + + public void GotoNextHomeRegion() + { + NextHomeRegionArgs e = new NextHomeRegionArgs(); + EventHandler handler = _NextHomeRegion; + handler?.Invoke(this, e); + } + protected bool login_auto_logout; protected long delay_group_fetch; protected Dictionary>> group_members_storage = new Dictionary>>(); diff --git a/Core/bottypes/athome.cs b/Core/bottypes/athome.cs deleted file mode 100644 index df86bbc..0000000 --- a/Core/bottypes/athome.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using OpenMetaverse; -using BetterSecondBotShared.Static; -using BetterSecondBotShared.logs; - -namespace BetterSecondBot.bottypes -{ - public class NextHomeRegionArgs: EventArgs - { - } - - public abstract class AtHome : MessageSwitcherBot - { - protected string BetterAtHomeAction = ""; - private EventHandler _ChangeSim; - private EventHandler _AlertMessage; - private EventHandler _LoginProgess; - private EventHandler _NextHomeRegion; - - private readonly object _ChangeSimLock = new object(); - private readonly object _AlertMessageLock = new object(); - private readonly object _LoginProgressLock = new object(); - private readonly object _NextHomeRegionLock = new object(); - - - public event EventHandler ChangeSimEvent - { - add { lock (_ChangeSimLock) { _ChangeSim += value; } } - remove { lock (_ChangeSimLock) { _ChangeSim -= value; } } - } - public event EventHandler AlertMessage - { - add { lock (_AlertMessageLock) { _AlertMessage += value; } } - remove { lock (_AlertMessageLock) { _AlertMessage -= value; } } - } - public event EventHandler LoginProgess - { - add { lock (_LoginProgressLock) { _LoginProgess += value; } } - remove { lock (_LoginProgressLock) { _LoginProgess -= value; } } - } - public event EventHandler NextHomeRegion - { - add { lock (_NextHomeRegionLock) { _NextHomeRegion += value; } } - remove { lock (_NextHomeRegionLock) { _NextHomeRegion -= value; } } - } - - protected override void LoginHandler(object o, LoginProgressEventArgs e) - { - base.LoginHandler(o, e); - EventHandler handler = _LoginProgess; - handler?.Invoke(this, e); - } - - protected void ChangeSim(object sender,SimChangedEventArgs e) - { - EventHandler handler = _ChangeSim; - handler?.Invoke(this, e); - } - - protected void AlertEvent(object sender,AlertMessageEventArgs e) - { - EventHandler handler = _AlertMessage; - handler?.Invoke(this, e); - } - - public void GotoNextHomeRegion() - { - NextHomeRegionArgs e = new NextHomeRegionArgs(); - EventHandler handler = _NextHomeRegion; - handler?.Invoke(this, e); - } - - public void ResetAtHome() - { - - } - - public void SetBetterAtHomeAction(string BetterAtHomeAction) - { - this.BetterAtHomeAction = BetterAtHomeAction; - } - - public override string GetStatus() - { - return base.GetStatus() + " <@BetterAtHome: " + BetterAtHomeAction + ">"; - } - - public string TeleportWithSLurl(string sl_url) - { - string[] bits = helpers.ParseSLurl(sl_url); - if (helpers.notempty(bits) == true) - { - if (bits.Length == 4) - { - float.TryParse(bits[1], out float posX); - float.TryParse(bits[2], out float posY); - float.TryParse(bits[3], out float posZ); - string regionName = bits[0]; - Client.Self.Teleport(regionName, new Vector3(posX, posY, posZ)); - return "ok"; - } - return "Invaild bits length for SLurl"; - } - return "No bits decoded"; - } - } -} diff --git a/Core/bottypes/loadorder.txt b/Core/bottypes/loadorder.txt index a456f07..e820b70 100644 --- a/Core/bottypes/loadorder.txt +++ b/Core/bottypes/loadorder.txt @@ -1,4 +1,4 @@ SecondBot -> DialogsBot -> ChatRelay -> RLVbot -> RLVbotHelper -> -AwaitReplyEventBot -> CommandsBot -> AVstorageBot -> AtHome -> +AwaitReplyEventBot -> CommandsBot -> AVstorageBot -> AttachableBot -> MessageSwitcherBot -> EventsBot -> VoidEventBot -> AnimationsBot -> VirtualsBot -> Log2FileBot -> ActionsAutoAcceptBot -> BasicBot \ No newline at end of file