123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- with Ada.Calendar;
- with Ada.Environment_Variables;
- with Ada.Exceptions;
- with Ada.Containers.Indefinite_Ordered_Maps;
- with Ada.IO_Exceptions;
- with Ada.Streams;
- with Ada.Strings.Fixed;
- with Ada.Strings.Unbounded;
- with Ada.Text_IO;
- with GNAT.MD5;
- with GNAT.Sockets;
- with ACL_Utils;
- use type Ada.Streams.Stream_Element_Count;
- procedure Ada_Lobby_Bot is
- package ATIO renames Ada.Text_IO;
- -- Direct conversion not possible... yes, sometimes Ada gets in the way...
- function To_String (Input : GNAT.MD5.Binary_Message_Digest) return String is
- Output : String (1 .. Input'Length);
- Output_Index : Positive := Output'First;
- begin
- for Input_Index in Input'Range loop
- Output (Output_Index) := Character'Val (Integer (Input (Input_Index)));
- Output_Index := Output_Index + 1;
- end loop;
- return Output;
- end To_String;
- function Get_Environment_Value (Name: String) return String is
- begin
- return Ada.Environment_Variables.Value (Name);
- exception
- when CONSTRAINT_ERROR =>
- raise CONSTRAINT_ERROR with "Required environment variable '" & Name & "' does not exist";
- end Get_Environment_Value;
- -- Sockets part
- package GS renames Gnat.Sockets;
- package ASU renames Ada.Strings.Unbounded;
- Username : constant String := Get_Environment_Value ("USERNAME");
- Raw_Password : constant String := Get_Environment_Value ("PASSWORD");
- -- password needs to be base64(md5(raw_password))
- Password : constant String := ACL_Utils.To_Base64 (To_String (GNAT.MD5.Digest (Raw_Password)));
- Client : GS.Socket_Type;
- Address : GS.Sock_Addr_Type;
- Channel : GS.Stream_Access;
- function Get_Line (Stream : in GNAT.Sockets.Stream_Access) return ASU.Unbounded_String is
- Ustring : ASU.Unbounded_String := ASU.Null_Unbounded_String;
- Ch : Character;
- begin
- loop
- Ch := Character'Input (Stream);
- exit when Ch = ASCII.LF;
- ASU.Append (Source => Ustring, New_Item => Ch);
- end loop;
- return Ustring;
- end Get_Line;
- use Ada.Calendar;
- PING_Period : constant Duration := 30.0;
- Next_PING_Time : Time := Clock + PING_Period;
- Line : ASU.Unbounded_String;
- Flood_Cooldown_Period : constant Duration := 1.0;
- Next_Message_Send_Time : Time := Clock;
- procedure Send_Message_To_Server (Channel: in GNAT.Sockets.Stream_Access; Message: String) is
- New_Line_String : constant String := (1 => ASCII.LF);
- begin
- delay until Next_Message_Send_Time;
- ATIO.Put_Line ("server <= '" & Message & "'");
- String'Write (Channel, Message & New_Line_String);
- Next_PING_Time := Clock + PING_Period;
- Next_Message_Send_Time := Clock + Flood_Cooldown_Period;
- end Send_Message_To_Server;
- type Spring_Lobby_Command_ID_Type is (
- ACCEPTED,
- BATTLEOPENED,
- DENIED,
- LOGININFOEND,
- SAID,
- SAIDPRIVATE,
- TASSERVER
- );
- type Spring_Lobby_Battle_Type is (Normal_Battle, Battle_Replay);
- subtype Spring_Lobby_Map_Hash_Type is Integer range -2**31 .. (2**31-1);
- type Spring_Lobby_NAT_Traversal_Type is (None, Hole_Punching, Fixed_Source_Ports);
- type Spring_Lobby_Passworded_Flag_Type is (False, True);
- type Spring_Lobby_Server_Mode_Type is (Normal_Mode, LAN_Mode);
- function To_String (Command_ID : Spring_Lobby_Command_ID_Type) return String is
- begin
- case Command_ID is
- when ACCEPTED => return "ACCEPTED";
- when BATTLEOPENED => return "BATTLEOPENED";
- when DENIED => return "DENIED";
- when LOGININFOEND => return "LOGININFOEND";
- when SAID => return "SAID";
- when SAIDPRIVATE => return "SAIDPRIVATE";
- when TASSERVER => return "TASServer";
- end case;
- end To_String;
- -----------------------------------------------------------------------------------------------
- -------------------------- SpringRTS Lobby Server Command Handlers ---------------------------
- -----------------------------------------------------------------------------------------------
- procedure Process_ACCEPTED (User_Name : String) is
- begin
- null;
- end Process_ACCEPTED;
- procedure Process_BATTLEOPENED (
- Battle_ID : String;
- Battle_Type : Spring_Lobby_Battle_Type;
- NAT_Type : Spring_Lobby_NAT_Traversal_Type;
- Founder : String;
- IP : String;
- Port : String;
- Max_Players : String;
- Passworded : Spring_Lobby_Passworded_Flag_Type;
- Rank : String;
- Map_Hash : Spring_Lobby_Map_Hash_Type;
- Map_Name : String;
- Title : String;
- Game_Name : String
- ) is
- begin
- null;
- end Process_BATTLEOPENED;
- procedure Process_DENIED (Reason : String) is
- begin
- ATIO.Put_Line ("DENIED Server login with this user account and password: '" & Reason & "', terminating!");
- Send_Message_To_Server (Channel, "EXIT due to login DENIED");
- end Process_DENIED;
- procedure Process_LOGININFOEND is
- begin
- -- I have received all the server-sent spam, now it is my turn!
- Send_Message_To_Server (Channel, "JOIN s44games");
- Send_Message_To_Server (Channel, "JOIN main");
- end Process_LOGININFOEND;
- procedure Process_SAID (Channel_Name : String; User_Name : String; Text : String) is
- begin
- ATIO.Put_Line ("SAID: " & User_Name & " said in " & Channel_Name & ": " & Text);
- if UserName = User_Name then return; end if; -- major lol ensued
- if Channel_Name /= "s44" and 0 /= Ada.Strings.Fixed.Index (Text, "#s44") then
- ATIO.Put_Line ("SAID: Someone mentioned #s44, responding");
- Send_Message_To_Server (Channel, "SAY " & Channel_Name & " Hi " & User_Name
- & ": If you are looking for joining #s44, type /join #s44 (I am a bot)"
- );
- end if;
- end Process_SAID;
- procedure Process_SAIDPRIVATE (User_Name : String; Text : String) is
- begin
- if Text = "!flood" then -- flood protect testing
- if User_Name = "ThinkIRC" then
- for I in 1 .. 8 loop
- Send_Message_To_Server (Channel, "SAYPRIVATE " & User_Name & " Flood!");
- end loop;
- end if;
- end if;
- end Process_SAIDPRIVATE;
- procedure Process_TASSERVER (
- Protocol_Version : String;
- Spring_Version : String;
- Helper_UDP_Port : String;
- Server_Mode : Spring_Lobby_Server_Mode_Type
- ) is
- begin
- null;
- end Process_TASSERVER;
- -----------------------------------------------------------------------------------------------
- -- ------------------------- SpringRTS Lobby Server Command Parsers ---------------------------
- -----------------------------------------------------------------------------------------------
- Parser_Failure : exception;
- function Expect_At (Where : String; What : Character; Index_Where : Positive) return Positive is
- begin
- if Where (Index_Where) /= What then
- raise Constraint_Error with "Mismatch at character " & Positive'Image (Index_Where - Where'First);
- end if;
- return Index_Where + 1;
- end Expect_At;
- procedure Raise_Exception_On_Leftovers (Where : in String;
- Index : in Positive
- ) is
- begin
- if Index /= (Where'Last + 1) then
- raise Parser_Failure with "leftover characters in message: '" & Where (Index .. Where'Last) & "'";
- end if;
- end;
- function Get_Token_Before_Delimiter (Where : in String;
- Delimiter : in Character;
- Index : in out Positive
- ) return String is
- Start_Index : constant Positive := Index;
- -- A word according to protocol ends with a space or if end of message is reached
- begin
- for Temp_Index in Start_Index .. Where'Last loop
- if Where (Temp_Index) = Delimiter then
- Index := Temp_Index + 1; -- Next thing after delimiter
- -- First case: delimiter found and is not right at the beginning
- if Temp_Index > Where'First then
- ATIO.Put ("Get_Token_Before_Delimiter: delimiter found. ");
- ATIO.Put_Line ("token: '" & Where (Start_Index .. (Temp_Index - 1)) & "'");
- return Where (Start_Index .. (Temp_Index - 1));
- else -- Second case: delimiter found, but is the first character.
- ATIO.Put_Line ("Get_Token_Before_Delimiter: delimiter found at start. ");
- return "";
- end if;
- end if;
- end loop;
- -- Third case: delimiter not found, return whole string after start index
- Index := Where'Last + 1;
- ATIO.Put ("Get_Token_Before_Delimiter: delimiter not found. ");
- ATIO.Put_Line ("token: '" & Where (Start_Index .. Where'Last) & "'");
- return Where (Start_Index .. Where'Last);
- end Get_Token_Before_Delimiter;
- -- ACCEPTED userName
- -- ^^^^^^^^^ already parsed by caller
- procedure Parse_ACCEPTED (Message : String) is
- Start_Index : constant Positive := Message'First + 8; -- Skip "ACCEPTED"
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- begin
- if UserName'Length < 1 then
- raise Parser_Failure with "userName field is empty?";
- end if;
- ATIO.Put_Line ("Parse_ACCEPTED: username: " & UserName);
- Raise_Exception_On_Leftovers (Message, Index);
- Process_ACCEPTED (User_Name => UserName);
- exception
- when Error: Parser_Failure =>
- ATIO.Put_Line ("Malformed ACCEPTED: " & Ada.Exceptions.Exception_Message (Error));
- return;
- end Parse_ACCEPTED;
- -- BATTLEOPENED battleID type natType founder ip port maxPlayers passworded rank mapHash {engineName} {engineVersion} {map} {title} {gameName}
- -- ^^^^^^^^^^^^^ already parsed by caller
- procedure Parse_BATTLEOPENED (Message : String) is
- Start_Index : constant Positive := Message'First + 12; -- Skip "BATTLEOPENED"
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- Bo_BattleID : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Type_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_NatType_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Founder : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Ip : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Port : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_MaxPlayers : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Passworded_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Rank : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_MapHash_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Bo_Map : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- Bo_Title : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- Bo_GameName : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- Bo_Type : Spring_Lobby_Battle_Type;
- Bo_NatType : Spring_Lobby_NAT_Traversal_Type;
- Bo_Passworded : Spring_Lobby_Passworded_Flag_Type;
- Bo_MapHash : Spring_Lobby_Map_Hash_Type;
- begin
- if Index <= Message'Last then
- ATIO.Put_Line ("Parse_BATTLEOPENED: leftover tokens in message: '" & Message (Index .. Message'Last) & "'");
- end if;
- ATIO.Put_Line ("Got battleID: '" & Bo_BattleID & "'");
- ATIO.Put_Line ("Got type: '" & Bo_Type_String & "'");
- ATIO.Put_Line ("Got natType: '" & Bo_NatType_String & "'");
- ATIO.Put_Line ("Got founder: '" & Bo_Founder & "'");
- ATIO.Put_Line ("Got ip: '" & Bo_Ip & "'");
- ATIO.Put_Line ("Got port: '" & Bo_Port & "'");
- ATIO.Put_Line ("Got maxPlayers: '" & Bo_MaxPlayers & "'");
- ATIO.Put_Line ("Got passworded: '" & Bo_Passworded_String & "'");
- ATIO.Put_Line ("Got rank: '" & Bo_Rank & "'");
- ATIO.Put_Line ("Got mapHash: '" & Bo_MapHash_String & "'");
- ATIO.Put_Line ("Got map: '" & Bo_Map & "'");
- ATIO.Put_Line ("Got title: '" & Bo_Title & "'");
- ATIO.Put_Line ("Got gameName: '" & Bo_GameName & "'");
- if Bo_Type_String = "0" then
- Bo_Type := Normal_Battle;
- elsif Bo_Type_String = "1" then
- Bo_Type := Battle_Replay;
- else
- raise Parser_Failure with "Unknown battle type: '" & Bo_Type_String & "'";
- end if;
- if Bo_NatType_String = "0" then
- Bo_NatType := None;
- elsif Bo_NatType_String = "1" then
- Bo_NatType := Hole_Punching;
- elsif Bo_NatType_String = "2" then
- Bo_NatType := Fixed_Source_Ports;
- else
- raise Parser_Failure with "Unknown NAT type: '" & Bo_NatType_String & "'";
- end if;
- if Bo_Passworded_String = "0" then
- Bo_Passworded := False;
- elsif Bo_Passworded_String = "1" then
- Bo_Passworded := True;
- else
- raise Parser_Failure with "Unknown value for field passworded: '" & Bo_Passworded_String & "'";
- end if;
- declare
- begin
- Bo_MapHash := Integer'Value (Bo_MapHash_String);
- exception
- when CONSTRAINT_ERROR =>
- raise Parser_Failure with "Value for mapHash is out of range: '" & Bo_MapHash_String & "'";
- end;
- Raise_Exception_On_Leftovers (Message, Index);
- Process_BATTLEOPENED (
- Battle_ID => Bo_BattleID,
- Battle_Type => Bo_Type,
- NAT_Type => Bo_NatType,
- Founder => Bo_Founder,
- IP => Bo_Ip,
- Port => Bo_Port,
- Max_Players => Bo_MaxPlayers,
- Passworded => Bo_Passworded,
- Rank => Bo_Rank,
- Map_Hash => Bo_MapHash,
- Map_Name => Bo_Map,
- Title => Bo_Title,
- Game_Name => Bo_GameName
- );
- exception
- when Error: Parser_Failure =>
- ATIO.Put_Line ("Malformed BATTLEOPENED: " & Ada.Exceptions.Exception_Message (Error));
- return;
- end Parse_BATTLEOPENED;
- -- DENIED {reason}
- -- ^^^^^^^ already parsed by caller
- procedure Parse_DENIED (Message : String) is
- Start_Index : constant Positive := Message'First + 6; -- Skip "DENIED" and a space
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- Reason : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- begin
- if Reason'Length = 0 then
- raise Parser_Failure with "reason not provided?!";
- end if;
- ATIO.Put_Line ("Parse_DENIED: reason: '" & Reason & "'");
- Raise_Exception_On_Leftovers (Message, Index);
- Process_DENIED (Reason => Reason);
- exception
- when Error: Parser_Failure =>
- ATIO.Put_Line ("Malformed DENIED: " & Ada.Exceptions.Exception_Message (Error));
- return;
- end Parse_DENIED;
- -- LOGININFOEND
- -- ^^^^^^^^^^^^ Already parsed by caller
- procedure Parse_LOGININFOEND (Message : String) is
- Index : constant Positive := Message'First + 12; -- Skip "LOGININFOEND"
- begin
- Raise_Exception_On_Leftovers (Message, Index);
- Process_LOGININFOEND;
- end Parse_LOGININFOEND;
- -- SAID chanName userName {message}
- -- ^^^^^ already parsed by caller
- procedure Parse_SAID (Message : String) is
- Start_Index : constant Positive := Message'First + 4; -- Skip "SAID"
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- ChanName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Text : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- begin
- if ChanName'Length = 0 then
- raise Parser_Failure with "chanName is empty?!";
- end if;
- ATIO.Put_Line ("Parse_SAID: chanName: " & ChanName & "'");
- if UserName'Length = 0 then
- raise Parser_Failure with "userName is empty?!";
- end if;
- ATIO.Put_Line ("Parse_SAID: userName: " & UserName & "'");
- if Text'Length = 0 then
- raise Parser_Failure with "text is empty?";
- end if;
- ATIO.Put_Line ("Parse_SAID: text: " & Text & "'");
- Raise_Exception_On_Leftovers (Message, Index);
- Process_SAID (
- Channel_Name => ChanName,
- User_Name => UserName,
- Text => Text
- );
- exception
- when Error: Parser_Failure =>
- ATIO.Put_Line ("Malformed SAID: " & Ada.Exceptions.Exception_Message (Error));
- return;
- end Parse_SAID;
- -- SAIDPRIVATE userName {message}
- -- ^^^^^^^^^^^^ already parsed by caller
- procedure Parse_SAIDPRIVATE (Message : String) is
- Start_Index : constant Positive := Message'First + 11; -- Skip "SAIDPRIVATE"
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- Text : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
- begin
- if UserName'Length = 0 then
- raise Parser_Failure with "userName is empty?!";
- end if;
- ATIO.Put_Line ("Parse_SAIDPRIVATE: userName: '" & UserName & "'");
- if Text'Length = 0 then
- raise Parser_Failure with "text is empty?!";
- end if;
- ATIO.Put_Line ("Parse_SAIDPRIVATE: text: '" & Text & "'");
- Raise_Exception_On_Leftovers (Message, Index);
- Process_SAIDPRIVATE (
- User_Name => UserName,
- Text => Text
- );
- exception
- when Error: Parser_Failure =>
- ATIO.Put_Line ("Malformed SAIDPRIVATE: " & Ada.Exceptions.Exception_Message (Error));
- return;
- end Parse_SAIDPRIVATE;
- -- TASSERVER protocolVersion springVersion udpPort serverMode
- -- ^^^^^^^^^^ already parsed by caller
- procedure Parse_TASSERVER (Message : String) is
- Start_Index : constant Positive := Message'First + 9; -- Skip "TASSERVER"
- Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
- ProtocolVersion : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- SpringVersion : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- UdpPort : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- ServerMode_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- ServerMode : Spring_Lobby_Server_Mode_Type;
- begin
- if ServerMode_String = "0" then
- ServerMode := Normal_Mode;
- elsif ServerMode_String = "1" then
- ServerMode := LAN_Mode;
- else
- raise Parser_Failure with "unsupported value for field serverMode: '" & ServerMode_String & "'";
- end if;
- Raise_Exception_On_Leftovers (Message, Index);
- Process_TASSERVER (
- Protocol_Version => ProtocolVersion,
- Spring_Version => SpringVersion,
- Helper_UDP_Port => UdpPort,
- Server_Mode => ServerMode
- );
- end Parse_TASSERVER;
- -----------------------------------------------------------------------------------------------
- -------------------- Dispatcher for SpringRTS Lobby Server Command Parsers --------------------
- -----------------------------------------------------------------------------------------------
- procedure Process_Message (Message: String) is
- package Map_Command_To_ID is new Ada.Containers.Indefinite_Ordered_Maps
- (Key_Type => String, Element_Type => Spring_Lobby_Command_ID_Type);
- Map : Map_Command_To_ID.Map;
- Index : Positive := Message'First;
- Command_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
- procedure Insert_To (Map : in out Map_Command_To_ID.Map;
- Command_ID : Spring_Lobby_Command_ID_Type
- ) is
- begin
- Map_Command_To_ID.Insert (Map, To_String (Command_ID), Command_ID);
- end Insert_To;
- begin
- Insert_To (Map, ACCEPTED);
- Insert_To (Map, BATTLEOPENED);
- Insert_To (Map, DENIED);
- Insert_To (Map, LOGININFOEND);
- Insert_To (Map, SAID);
- Insert_To (Map, SAIDPRIVATE);
- Insert_To (Map, TASSERVER);
- ATIO.Put_Line ("server => " & Message);
- declare
- Cursor : constant Map_Command_To_ID.Cursor := Map_Command_To_ID.Find (Map, Command_String);
- use type Map_Command_To_ID.Cursor;
- begin
- if Cursor = Map_Command_To_ID.No_Element then
- ATIO.Put_Line ("Warning: no parser available for lobby command: '"
- & Command_String & "'");
- return; -- short circuit to keep the indents lower
- end if;
- case Map_Command_To_ID.Element (Cursor) is
- when ACCEPTED => Parse_ACCEPTED (Message);
- when BATTLEOPENED => Parse_BATTLEOPENED (Message);
- when DENIED => Parse_DENIED (Message);
- when LOGININFOEND => Parse_LOGININFOEND (Message);
- when SAID => Parse_SAID (Message);
- when SAIDPRIVATE => Parse_SAIDPRIVATE (Message);
- when TASSERVER => Parse_TASSERVER (Message);
- end case;
- end;
- end Process_Message;
- begin
- -- ATIO.put_line ("MD5: " & ct.to_hex (md5string)); -- broken atm, should convert to hex
- ATIO.Put_Line ("MD5+base64: " & Password);
- ATIO.Put_Line ("Connecting to lobby...");
- GS.Create_Socket (Client);
- Address.Addr := GS.Addresses (GS.Get_Host_By_Name ("lobby.springrts.com"), 1); -- get first address (official?)
- Address.Port := 8200;
- GS.Connect_Socket (Client, Address);
- Channel := GS.Stream (Client);
- Send_Message_To_Server (Channel, "LOGIN " & Username & " " & Password & " 0 * AdaLobby/Bot 0.1");
- -- main loop:
- loop
- select
- delay until Next_PING_Time;
- ATIO.Put_Line (" Timeout");
- Send_Message_To_Server (Channel, "PING");
- Next_PING_Time := Clock + PING_Period;
- then abort
- Line := Get_Line (Channel);
- Process_Message (ASU.To_String (Line));
- end select;
- end loop;
- exception
- when Ada.IO_Exceptions.End_Error =>
- ATIO.Put_Line ("Socket closed, exiting.");
- end Ada_Lobby_Bot;
|