ada_lobby_bot.adb 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. with Ada.Calendar;
  2. with Ada.Environment_Variables;
  3. with Ada.Exceptions;
  4. with Ada.Containers.Indefinite_Ordered_Maps;
  5. with Ada.IO_Exceptions;
  6. with Ada.Streams;
  7. with Ada.Strings.Fixed;
  8. with Ada.Strings.Unbounded;
  9. with Ada.Text_IO;
  10. with GNAT.MD5;
  11. with GNAT.Sockets;
  12. with ACL_Utils;
  13. use type Ada.Streams.Stream_Element_Count;
  14. procedure Ada_Lobby_Bot is
  15. package ATIO renames Ada.Text_IO;
  16. -- Direct conversion not possible... yes, sometimes Ada gets in the way...
  17. function To_String (Input : GNAT.MD5.Binary_Message_Digest) return String is
  18. Output : String (1 .. Input'Length);
  19. Output_Index : Positive := Output'First;
  20. begin
  21. for Input_Index in Input'Range loop
  22. Output (Output_Index) := Character'Val (Integer (Input (Input_Index)));
  23. Output_Index := Output_Index + 1;
  24. end loop;
  25. return Output;
  26. end To_String;
  27. function Get_Environment_Value (Name: String) return String is
  28. begin
  29. return Ada.Environment_Variables.Value (Name);
  30. exception
  31. when CONSTRAINT_ERROR =>
  32. raise CONSTRAINT_ERROR with "Required environment variable '" & Name & "' does not exist";
  33. end Get_Environment_Value;
  34. -- Sockets part
  35. package GS renames Gnat.Sockets;
  36. package ASU renames Ada.Strings.Unbounded;
  37. Username : constant String := Get_Environment_Value ("USERNAME");
  38. Raw_Password : constant String := Get_Environment_Value ("PASSWORD");
  39. -- password needs to be base64(md5(raw_password))
  40. Password : constant String := ACL_Utils.To_Base64 (To_String (GNAT.MD5.Digest (Raw_Password)));
  41. Client : GS.Socket_Type;
  42. Address : GS.Sock_Addr_Type;
  43. Channel : GS.Stream_Access;
  44. function Get_Line (Stream : in GNAT.Sockets.Stream_Access) return ASU.Unbounded_String is
  45. Ustring : ASU.Unbounded_String := ASU.Null_Unbounded_String;
  46. Ch : Character;
  47. begin
  48. loop
  49. Ch := Character'Input (Stream);
  50. exit when Ch = ASCII.LF;
  51. ASU.Append (Source => Ustring, New_Item => Ch);
  52. end loop;
  53. return Ustring;
  54. end Get_Line;
  55. use Ada.Calendar;
  56. PING_Period : constant Duration := 30.0;
  57. Next_PING_Time : Time := Clock + PING_Period;
  58. Line : ASU.Unbounded_String;
  59. Flood_Cooldown_Period : constant Duration := 1.0;
  60. Next_Message_Send_Time : Time := Clock;
  61. procedure Send_Message_To_Server (Channel: in GNAT.Sockets.Stream_Access; Message: String) is
  62. New_Line_String : constant String := (1 => ASCII.LF);
  63. begin
  64. delay until Next_Message_Send_Time;
  65. ATIO.Put_Line ("server <= '" & Message & "'");
  66. String'Write (Channel, Message & New_Line_String);
  67. Next_PING_Time := Clock + PING_Period;
  68. Next_Message_Send_Time := Clock + Flood_Cooldown_Period;
  69. end Send_Message_To_Server;
  70. type Spring_Lobby_Command_ID_Type is (
  71. ACCEPTED,
  72. BATTLEOPENED,
  73. DENIED,
  74. LOGININFOEND,
  75. SAID,
  76. SAIDPRIVATE,
  77. TASSERVER
  78. );
  79. type Spring_Lobby_Battle_Type is (Normal_Battle, Battle_Replay);
  80. subtype Spring_Lobby_Map_Hash_Type is Integer range -2**31 .. (2**31-1);
  81. type Spring_Lobby_NAT_Traversal_Type is (None, Hole_Punching, Fixed_Source_Ports);
  82. type Spring_Lobby_Passworded_Flag_Type is (False, True);
  83. type Spring_Lobby_Server_Mode_Type is (Normal_Mode, LAN_Mode);
  84. function To_String (Command_ID : Spring_Lobby_Command_ID_Type) return String is
  85. begin
  86. case Command_ID is
  87. when ACCEPTED => return "ACCEPTED";
  88. when BATTLEOPENED => return "BATTLEOPENED";
  89. when DENIED => return "DENIED";
  90. when LOGININFOEND => return "LOGININFOEND";
  91. when SAID => return "SAID";
  92. when SAIDPRIVATE => return "SAIDPRIVATE";
  93. when TASSERVER => return "TASServer";
  94. end case;
  95. end To_String;
  96. -----------------------------------------------------------------------------------------------
  97. -------------------------- SpringRTS Lobby Server Command Handlers ---------------------------
  98. -----------------------------------------------------------------------------------------------
  99. procedure Process_ACCEPTED (User_Name : String) is
  100. begin
  101. null;
  102. end Process_ACCEPTED;
  103. procedure Process_BATTLEOPENED (
  104. Battle_ID : String;
  105. Battle_Type : Spring_Lobby_Battle_Type;
  106. NAT_Type : Spring_Lobby_NAT_Traversal_Type;
  107. Founder : String;
  108. IP : String;
  109. Port : String;
  110. Max_Players : String;
  111. Passworded : Spring_Lobby_Passworded_Flag_Type;
  112. Rank : String;
  113. Map_Hash : Spring_Lobby_Map_Hash_Type;
  114. Map_Name : String;
  115. Title : String;
  116. Game_Name : String
  117. ) is
  118. begin
  119. null;
  120. end Process_BATTLEOPENED;
  121. procedure Process_DENIED (Reason : String) is
  122. begin
  123. ATIO.Put_Line ("DENIED Server login with this user account and password: '" & Reason & "', terminating!");
  124. Send_Message_To_Server (Channel, "EXIT due to login DENIED");
  125. end Process_DENIED;
  126. procedure Process_LOGININFOEND is
  127. begin
  128. -- I have received all the server-sent spam, now it is my turn!
  129. Send_Message_To_Server (Channel, "JOIN s44games");
  130. Send_Message_To_Server (Channel, "JOIN main");
  131. end Process_LOGININFOEND;
  132. procedure Process_SAID (Channel_Name : String; User_Name : String; Text : String) is
  133. begin
  134. ATIO.Put_Line ("SAID: " & User_Name & " said in " & Channel_Name & ": " & Text);
  135. if UserName = User_Name then return; end if; -- major lol ensued
  136. if Channel_Name /= "s44" and 0 /= Ada.Strings.Fixed.Index (Text, "#s44") then
  137. ATIO.Put_Line ("SAID: Someone mentioned #s44, responding");
  138. Send_Message_To_Server (Channel, "SAY " & Channel_Name & " Hi " & User_Name
  139. & ": If you are looking for joining #s44, type /join #s44 (I am a bot)"
  140. );
  141. end if;
  142. end Process_SAID;
  143. procedure Process_SAIDPRIVATE (User_Name : String; Text : String) is
  144. begin
  145. if Text = "!flood" then -- flood protect testing
  146. if User_Name = "ThinkIRC" then
  147. for I in 1 .. 8 loop
  148. Send_Message_To_Server (Channel, "SAYPRIVATE " & User_Name & " Flood!");
  149. end loop;
  150. end if;
  151. end if;
  152. end Process_SAIDPRIVATE;
  153. procedure Process_TASSERVER (
  154. Protocol_Version : String;
  155. Spring_Version : String;
  156. Helper_UDP_Port : String;
  157. Server_Mode : Spring_Lobby_Server_Mode_Type
  158. ) is
  159. begin
  160. null;
  161. end Process_TASSERVER;
  162. -----------------------------------------------------------------------------------------------
  163. -- ------------------------- SpringRTS Lobby Server Command Parsers ---------------------------
  164. -----------------------------------------------------------------------------------------------
  165. Parser_Failure : exception;
  166. function Expect_At (Where : String; What : Character; Index_Where : Positive) return Positive is
  167. begin
  168. if Where (Index_Where) /= What then
  169. raise Constraint_Error with "Mismatch at character " & Positive'Image (Index_Where - Where'First);
  170. end if;
  171. return Index_Where + 1;
  172. end Expect_At;
  173. procedure Raise_Exception_On_Leftovers (Where : in String;
  174. Index : in Positive
  175. ) is
  176. begin
  177. if Index /= (Where'Last + 1) then
  178. raise Parser_Failure with "leftover characters in message: '" & Where (Index .. Where'Last) & "'";
  179. end if;
  180. end;
  181. function Get_Token_Before_Delimiter (Where : in String;
  182. Delimiter : in Character;
  183. Index : in out Positive
  184. ) return String is
  185. Start_Index : constant Positive := Index;
  186. -- A word according to protocol ends with a space or if end of message is reached
  187. begin
  188. for Temp_Index in Start_Index .. Where'Last loop
  189. if Where (Temp_Index) = Delimiter then
  190. Index := Temp_Index + 1; -- Next thing after delimiter
  191. -- First case: delimiter found and is not right at the beginning
  192. if Temp_Index > Where'First then
  193. ATIO.Put ("Get_Token_Before_Delimiter: delimiter found. ");
  194. ATIO.Put_Line ("token: '" & Where (Start_Index .. (Temp_Index - 1)) & "'");
  195. return Where (Start_Index .. (Temp_Index - 1));
  196. else -- Second case: delimiter found, but is the first character.
  197. ATIO.Put_Line ("Get_Token_Before_Delimiter: delimiter found at start. ");
  198. return "";
  199. end if;
  200. end if;
  201. end loop;
  202. -- Third case: delimiter not found, return whole string after start index
  203. Index := Where'Last + 1;
  204. ATIO.Put ("Get_Token_Before_Delimiter: delimiter not found. ");
  205. ATIO.Put_Line ("token: '" & Where (Start_Index .. Where'Last) & "'");
  206. return Where (Start_Index .. Where'Last);
  207. end Get_Token_Before_Delimiter;
  208. -- ACCEPTED userName
  209. -- ^^^^^^^^^ already parsed by caller
  210. procedure Parse_ACCEPTED (Message : String) is
  211. Start_Index : constant Positive := Message'First + 8; -- Skip "ACCEPTED"
  212. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  213. UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  214. begin
  215. if UserName'Length < 1 then
  216. raise Parser_Failure with "userName field is empty?";
  217. end if;
  218. ATIO.Put_Line ("Parse_ACCEPTED: username: " & UserName);
  219. Raise_Exception_On_Leftovers (Message, Index);
  220. Process_ACCEPTED (User_Name => UserName);
  221. exception
  222. when Error: Parser_Failure =>
  223. ATIO.Put_Line ("Malformed ACCEPTED: " & Ada.Exceptions.Exception_Message (Error));
  224. return;
  225. end Parse_ACCEPTED;
  226. -- BATTLEOPENED battleID type natType founder ip port maxPlayers passworded rank mapHash {engineName} {engineVersion} {map} {title} {gameName}
  227. -- ^^^^^^^^^^^^^ already parsed by caller
  228. procedure Parse_BATTLEOPENED (Message : String) is
  229. Start_Index : constant Positive := Message'First + 12; -- Skip "BATTLEOPENED"
  230. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  231. Bo_BattleID : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  232. Bo_Type_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  233. Bo_NatType_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  234. Bo_Founder : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  235. Bo_Ip : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  236. Bo_Port : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  237. Bo_MaxPlayers : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  238. Bo_Passworded_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  239. Bo_Rank : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  240. Bo_MapHash_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  241. Bo_Map : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  242. Bo_Title : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  243. Bo_GameName : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  244. Bo_Type : Spring_Lobby_Battle_Type;
  245. Bo_NatType : Spring_Lobby_NAT_Traversal_Type;
  246. Bo_Passworded : Spring_Lobby_Passworded_Flag_Type;
  247. Bo_MapHash : Spring_Lobby_Map_Hash_Type;
  248. begin
  249. if Index <= Message'Last then
  250. ATIO.Put_Line ("Parse_BATTLEOPENED: leftover tokens in message: '" & Message (Index .. Message'Last) & "'");
  251. end if;
  252. ATIO.Put_Line ("Got battleID: '" & Bo_BattleID & "'");
  253. ATIO.Put_Line ("Got type: '" & Bo_Type_String & "'");
  254. ATIO.Put_Line ("Got natType: '" & Bo_NatType_String & "'");
  255. ATIO.Put_Line ("Got founder: '" & Bo_Founder & "'");
  256. ATIO.Put_Line ("Got ip: '" & Bo_Ip & "'");
  257. ATIO.Put_Line ("Got port: '" & Bo_Port & "'");
  258. ATIO.Put_Line ("Got maxPlayers: '" & Bo_MaxPlayers & "'");
  259. ATIO.Put_Line ("Got passworded: '" & Bo_Passworded_String & "'");
  260. ATIO.Put_Line ("Got rank: '" & Bo_Rank & "'");
  261. ATIO.Put_Line ("Got mapHash: '" & Bo_MapHash_String & "'");
  262. ATIO.Put_Line ("Got map: '" & Bo_Map & "'");
  263. ATIO.Put_Line ("Got title: '" & Bo_Title & "'");
  264. ATIO.Put_Line ("Got gameName: '" & Bo_GameName & "'");
  265. if Bo_Type_String = "0" then
  266. Bo_Type := Normal_Battle;
  267. elsif Bo_Type_String = "1" then
  268. Bo_Type := Battle_Replay;
  269. else
  270. raise Parser_Failure with "Unknown battle type: '" & Bo_Type_String & "'";
  271. end if;
  272. if Bo_NatType_String = "0" then
  273. Bo_NatType := None;
  274. elsif Bo_NatType_String = "1" then
  275. Bo_NatType := Hole_Punching;
  276. elsif Bo_NatType_String = "2" then
  277. Bo_NatType := Fixed_Source_Ports;
  278. else
  279. raise Parser_Failure with "Unknown NAT type: '" & Bo_NatType_String & "'";
  280. end if;
  281. if Bo_Passworded_String = "0" then
  282. Bo_Passworded := False;
  283. elsif Bo_Passworded_String = "1" then
  284. Bo_Passworded := True;
  285. else
  286. raise Parser_Failure with "Unknown value for field passworded: '" & Bo_Passworded_String & "'";
  287. end if;
  288. declare
  289. begin
  290. Bo_MapHash := Integer'Value (Bo_MapHash_String);
  291. exception
  292. when CONSTRAINT_ERROR =>
  293. raise Parser_Failure with "Value for mapHash is out of range: '" & Bo_MapHash_String & "'";
  294. end;
  295. Raise_Exception_On_Leftovers (Message, Index);
  296. Process_BATTLEOPENED (
  297. Battle_ID => Bo_BattleID,
  298. Battle_Type => Bo_Type,
  299. NAT_Type => Bo_NatType,
  300. Founder => Bo_Founder,
  301. IP => Bo_Ip,
  302. Port => Bo_Port,
  303. Max_Players => Bo_MaxPlayers,
  304. Passworded => Bo_Passworded,
  305. Rank => Bo_Rank,
  306. Map_Hash => Bo_MapHash,
  307. Map_Name => Bo_Map,
  308. Title => Bo_Title,
  309. Game_Name => Bo_GameName
  310. );
  311. exception
  312. when Error: Parser_Failure =>
  313. ATIO.Put_Line ("Malformed BATTLEOPENED: " & Ada.Exceptions.Exception_Message (Error));
  314. return;
  315. end Parse_BATTLEOPENED;
  316. -- DENIED {reason}
  317. -- ^^^^^^^ already parsed by caller
  318. procedure Parse_DENIED (Message : String) is
  319. Start_Index : constant Positive := Message'First + 6; -- Skip "DENIED" and a space
  320. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  321. Reason : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  322. begin
  323. if Reason'Length = 0 then
  324. raise Parser_Failure with "reason not provided?!";
  325. end if;
  326. ATIO.Put_Line ("Parse_DENIED: reason: '" & Reason & "'");
  327. Raise_Exception_On_Leftovers (Message, Index);
  328. Process_DENIED (Reason => Reason);
  329. exception
  330. when Error: Parser_Failure =>
  331. ATIO.Put_Line ("Malformed DENIED: " & Ada.Exceptions.Exception_Message (Error));
  332. return;
  333. end Parse_DENIED;
  334. -- LOGININFOEND
  335. -- ^^^^^^^^^^^^ Already parsed by caller
  336. procedure Parse_LOGININFOEND (Message : String) is
  337. Index : constant Positive := Message'First + 12; -- Skip "LOGININFOEND"
  338. begin
  339. Raise_Exception_On_Leftovers (Message, Index);
  340. Process_LOGININFOEND;
  341. end Parse_LOGININFOEND;
  342. -- SAID chanName userName {message}
  343. -- ^^^^^ already parsed by caller
  344. procedure Parse_SAID (Message : String) is
  345. Start_Index : constant Positive := Message'First + 4; -- Skip "SAID"
  346. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  347. ChanName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  348. UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  349. Text : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  350. begin
  351. if ChanName'Length = 0 then
  352. raise Parser_Failure with "chanName is empty?!";
  353. end if;
  354. ATIO.Put_Line ("Parse_SAID: chanName: " & ChanName & "'");
  355. if UserName'Length = 0 then
  356. raise Parser_Failure with "userName is empty?!";
  357. end if;
  358. ATIO.Put_Line ("Parse_SAID: userName: " & UserName & "'");
  359. if Text'Length = 0 then
  360. raise Parser_Failure with "text is empty?";
  361. end if;
  362. ATIO.Put_Line ("Parse_SAID: text: " & Text & "'");
  363. Raise_Exception_On_Leftovers (Message, Index);
  364. Process_SAID (
  365. Channel_Name => ChanName,
  366. User_Name => UserName,
  367. Text => Text
  368. );
  369. exception
  370. when Error: Parser_Failure =>
  371. ATIO.Put_Line ("Malformed SAID: " & Ada.Exceptions.Exception_Message (Error));
  372. return;
  373. end Parse_SAID;
  374. -- SAIDPRIVATE userName {message}
  375. -- ^^^^^^^^^^^^ already parsed by caller
  376. procedure Parse_SAIDPRIVATE (Message : String) is
  377. Start_Index : constant Positive := Message'First + 11; -- Skip "SAIDPRIVATE"
  378. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  379. UserName : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  380. Text : constant String := Get_Token_Before_Delimiter (Message, ASCII.HT, Index);
  381. begin
  382. if UserName'Length = 0 then
  383. raise Parser_Failure with "userName is empty?!";
  384. end if;
  385. ATIO.Put_Line ("Parse_SAIDPRIVATE: userName: '" & UserName & "'");
  386. if Text'Length = 0 then
  387. raise Parser_Failure with "text is empty?!";
  388. end if;
  389. ATIO.Put_Line ("Parse_SAIDPRIVATE: text: '" & Text & "'");
  390. Raise_Exception_On_Leftovers (Message, Index);
  391. Process_SAIDPRIVATE (
  392. User_Name => UserName,
  393. Text => Text
  394. );
  395. exception
  396. when Error: Parser_Failure =>
  397. ATIO.Put_Line ("Malformed SAIDPRIVATE: " & Ada.Exceptions.Exception_Message (Error));
  398. return;
  399. end Parse_SAIDPRIVATE;
  400. -- TASSERVER protocolVersion springVersion udpPort serverMode
  401. -- ^^^^^^^^^^ already parsed by caller
  402. procedure Parse_TASSERVER (Message : String) is
  403. Start_Index : constant Positive := Message'First + 9; -- Skip "TASSERVER"
  404. Index : Positive := Expect_At (Message, ' ', Start_Index); -- First delimiter
  405. ProtocolVersion : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  406. SpringVersion : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  407. UdpPort : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  408. ServerMode_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  409. ServerMode : Spring_Lobby_Server_Mode_Type;
  410. begin
  411. if ServerMode_String = "0" then
  412. ServerMode := Normal_Mode;
  413. elsif ServerMode_String = "1" then
  414. ServerMode := LAN_Mode;
  415. else
  416. raise Parser_Failure with "unsupported value for field serverMode: '" & ServerMode_String & "'";
  417. end if;
  418. Raise_Exception_On_Leftovers (Message, Index);
  419. Process_TASSERVER (
  420. Protocol_Version => ProtocolVersion,
  421. Spring_Version => SpringVersion,
  422. Helper_UDP_Port => UdpPort,
  423. Server_Mode => ServerMode
  424. );
  425. end Parse_TASSERVER;
  426. -----------------------------------------------------------------------------------------------
  427. -------------------- Dispatcher for SpringRTS Lobby Server Command Parsers --------------------
  428. -----------------------------------------------------------------------------------------------
  429. procedure Process_Message (Message: String) is
  430. package Map_Command_To_ID is new Ada.Containers.Indefinite_Ordered_Maps
  431. (Key_Type => String, Element_Type => Spring_Lobby_Command_ID_Type);
  432. Map : Map_Command_To_ID.Map;
  433. Index : Positive := Message'First;
  434. Command_String : constant String := Get_Token_Before_Delimiter (Message, ' ', Index);
  435. procedure Insert_To (Map : in out Map_Command_To_ID.Map;
  436. Command_ID : Spring_Lobby_Command_ID_Type
  437. ) is
  438. begin
  439. Map_Command_To_ID.Insert (Map, To_String (Command_ID), Command_ID);
  440. end Insert_To;
  441. begin
  442. Insert_To (Map, ACCEPTED);
  443. Insert_To (Map, BATTLEOPENED);
  444. Insert_To (Map, DENIED);
  445. Insert_To (Map, LOGININFOEND);
  446. Insert_To (Map, SAID);
  447. Insert_To (Map, SAIDPRIVATE);
  448. Insert_To (Map, TASSERVER);
  449. ATIO.Put_Line ("server => " & Message);
  450. declare
  451. Cursor : constant Map_Command_To_ID.Cursor := Map_Command_To_ID.Find (Map, Command_String);
  452. use type Map_Command_To_ID.Cursor;
  453. begin
  454. if Cursor = Map_Command_To_ID.No_Element then
  455. ATIO.Put_Line ("Warning: no parser available for lobby command: '"
  456. & Command_String & "'");
  457. return; -- short circuit to keep the indents lower
  458. end if;
  459. case Map_Command_To_ID.Element (Cursor) is
  460. when ACCEPTED => Parse_ACCEPTED (Message);
  461. when BATTLEOPENED => Parse_BATTLEOPENED (Message);
  462. when DENIED => Parse_DENIED (Message);
  463. when LOGININFOEND => Parse_LOGININFOEND (Message);
  464. when SAID => Parse_SAID (Message);
  465. when SAIDPRIVATE => Parse_SAIDPRIVATE (Message);
  466. when TASSERVER => Parse_TASSERVER (Message);
  467. end case;
  468. end;
  469. end Process_Message;
  470. begin
  471. -- ATIO.put_line ("MD5: " & ct.to_hex (md5string)); -- broken atm, should convert to hex
  472. ATIO.Put_Line ("MD5+base64: " & Password);
  473. ATIO.Put_Line ("Connecting to lobby...");
  474. GS.Create_Socket (Client);
  475. Address.Addr := GS.Addresses (GS.Get_Host_By_Name ("lobby.springrts.com"), 1); -- get first address (official?)
  476. Address.Port := 8200;
  477. GS.Connect_Socket (Client, Address);
  478. Channel := GS.Stream (Client);
  479. Send_Message_To_Server (Channel, "LOGIN " & Username & " " & Password & " 0 * AdaLobby/Bot 0.1");
  480. -- main loop:
  481. loop
  482. select
  483. delay until Next_PING_Time;
  484. ATIO.Put_Line (" Timeout");
  485. Send_Message_To_Server (Channel, "PING");
  486. Next_PING_Time := Clock + PING_Period;
  487. then abort
  488. Line := Get_Line (Channel);
  489. Process_Message (ASU.To_String (Line));
  490. end select;
  491. end loop;
  492. exception
  493. when Ada.IO_Exceptions.End_Error =>
  494. ATIO.Put_Line ("Socket closed, exiting.");
  495. end Ada_Lobby_Bot;