The code below relies on a configuration file called Convey.config
that is placed in the same directory as the Convey executable. A sample configuration file is the following:
firstname:Convey lastname:Sextant password:$1$cbde97a19118750e02f054accf10a269 grid:https://login.agni.lindenlab.com/cgi-bin/login.cgi secret:C55OPS master:dd372c91-16a2-4414-a8b5-ec1312637ff0
The following parameters must be configured:
firstname
- the first name of the bot that will connect to the server.lastname
- the last name of the bot that will connect to the server.password
- the password of the bot that will connect to the server. This can be a plain-text password or an MD5 hash prefixed by $1$
.grid
- the grid login-URI, for Second Life, this setting does not need to be changed.secret
- this is an shared-secret between the primitive script and the bot, any alpha-numeric password will do.master
- this is the master of the bot, set this to your avatar UUID.If you have downloaded the binaries, you can skip this step. It is provided here in case you want to modify or recompile Convey.
From the program folder, provided you have mono
installed and you are on a UNIX system, issue:
dmcs Convey.cs -r:libomv/OpenMetaverse.dll -r:libomv/OpenMetaverseTypes.dll
on the command line to compile the bot.
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2012 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// using System; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Net; using System.Threading; using System.Collections.Generic; using OpenMetaverse; using System.Linq; namespace Convey { internal class Convey { private static readonly GridClient Client = new GridClient(); private static string _firstName = string.Empty; private static string _lastName = string.Empty; private static string _password = string.Empty; private static List < UUID > _masters = new List < UUID > (); private static readonly Thread MainConnectionThread = new Thread(MainConnection); private static Dictionary < char, bool > StabilizeChecks = new Dictionary < char, bool > () { { 'l', false }, { 'e', false }, { 's', false }, { 'a', false } }; private static List < string > AuthAgents = new List < string > (); private static volatile bool _run = true; private static volatile string _url = string.Empty; public static void Main() { Settings.LOG_LEVEL = Helpers.LogLevel.None; Client.Settings.STORE_LAND_PATCHES = true; Client.Settings.ALWAYS_DECODE_OBJECTS = true; Client.Settings.ALWAYS_REQUEST_OBJECTS = true; Client.Settings.SEND_AGENT_UPDATES = true; Client.Settings.USE_ASSET_CACHE = false; Client.Settings.FETCH_MISSING_INVENTORY = true; Client.Network.LoginProgress += HandleLoginProgress; Client.Appearance.AppearanceSet += HandleAppearanceSet; Client.Network.SimConnected += HandleSimulatorConnected; Client.Network.Disconnected += HandleDisconnected; Client.Network.SimDisconnected += HandleSimulatorDisconnected; Client.Network.EventQueueRunning += HandleEventQueueRunning; Client.Friends.FriendshipOffered += HandleFriendshipOffered; Client.Self.IM += HandleSelfIM; try { foreach(string cfg in File.ReadLines("Convey.config")) { string[] data = cfg.Split(new char[] { ':' }, 2); switch (data[0]) { case "firstname": _firstName = data[1]; break; case "lastname": _lastName = data[1]; break; case "password": _password = data[1]; break; case "grid": Client.Settings.LOGIN_SERVER = data[1]; break; // auth-secrets and masters are read dynamically on request. case "master": break; case "secret": break; default: Console.WriteLine("[Convey] : Garbled line: " + cfg + ", the program will now terminate."); System.Environment.Exit(1); break; } } } catch (FileNotFoundException) { Console.WriteLine("[Convey] : Could not read configuration file."); System.Environment.Exit(1); } new List < int > (new int[Console.WindowWidth]).ForEach(i => Console.Write("-")); MainConnectionThread.Start(); } private static void HandleDisconnected(object sender, DisconnectedEventArgs e) { Console.Write("\n[Convey] : Disconnected..."); if (!StabilizeChecks.ContainsKey('l')) { StabilizeChecks.Add('l', false); return; } StabilizeChecks['l'] = false; } private static void HandleEventQueueRunning(object sender, EventQueueRunningEventArgs e) { Console.Write("\n[Convey] : Event queue started..."); if (!StabilizeChecks.ContainsKey('e')) { StabilizeChecks.Add('e', true); return; } StabilizeChecks['e'] = true; } private static void HandleSimulatorConnected(object sender, SimConnectedEventArgs e) { Console.Write("\n[Convey] : Simulator connected..."); if (!StabilizeChecks.ContainsKey('s')) { StabilizeChecks.Add('s', true); return; } StabilizeChecks['s'] = true; } private static void HandleSimulatorDisconnected(object sender, SimDisconnectedEventArgs e) { if (Client.Network.Simulators.Count >= 1) return; Console.Write("\n[Convey] : Simulators disconnected..."); if (!StabilizeChecks.ContainsKey('s')) { StabilizeChecks.Add('s', false); return; } StabilizeChecks['s'] = false; } private static void HandleAppearanceSet(object sender, AppearanceSetEventArgs e) { if (e.Success) { Console.Write("\n[Convey] : Appearance set..."); if (!StabilizeChecks.ContainsKey('a')) { StabilizeChecks.Add('a', true); return; } StabilizeChecks['a'] = true; return; } Console.Write("\n[Convey] : Failed to set appearance..."); if (!StabilizeChecks.ContainsKey('a')) { StabilizeChecks.Add('a', false); return; } StabilizeChecks['a'] = false; } private static void HandleLoginProgress(object sender, LoginProgressEventArgs e) { switch (e.Status) { case LoginStatus.Success: Console.Write("\n[Convey] : Login ok..."); StabilizeChecks['l'] = true; break; case LoginStatus.Failed: Console.Write("\n[Convey] : Failed Login..."); if (!StabilizeChecks.ContainsKey('l')) { StabilizeChecks.Add('l', false); break; } StabilizeChecks['l'] = false; break; } } private static void HandleFriendshipOffered(object sender, FriendshipOfferedEventArgs e) { if (!_masters.Contains(e.AgentID)) return; Console.Write("\n[Convey] : Accepting friendship with " + e.AgentName + "."); Client.Friends.AcceptFriendship(e.AgentID, e.SessionID); } private static void MainConnection() { StabilizeChecks = new Dictionary < char, bool > (); Client.Network.BeginLogin(new LoginParams(Client, _firstName, _lastName, _password, "[WaS] Corrade", "1.0")); Console.Write("\n[Corrade] : Stabilizing, please wait..."); do { Console.Write("."); Thread.Sleep(1000); } while (StabilizeChecks.Count != 4 && !StabilizeChecks.ContainsValue(false)); if (!StabilizeChecks.ContainsValue(false)) Console.Write("\n[Corrade] : Fully connected and accepting commands."); do { Thread.Sleep(1000); if (StabilizeChecks.ContainsValue(false)) { Console.Write("\n[Corrade] : Disconnected, shutting down..."); _run = false; } } while (_run); Console.Write("\n[Corrade] : All operations completed. Quitting."); new List < int > (new int[Console.WindowWidth]).ForEach(i => Console.Write("-")); Client.Network.Logout(); Client.Network.Shutdown(NetworkManager.DisconnectType.ClientInitiated); } private static void HandleSelfIM(object sender, InstantMessageEventArgs e) { if (e.IM.Dialog.Equals(InstantMessageDialog.MessageBox)) { Console.Write("\n[Convey] : [Server Message] : " + e.IM.Message.Replace(System.Environment.NewLine, " ")); return; } if (e.IM.Dialog.Equals(InstantMessageDialog.RequestTeleport)) { try { foreach(string cfg in File.ReadLines("Convey.config")) { string[] data = cfg.Split(new char[] { ':' }, 3); switch (data[0]) { case "master": var master = UUID.Zero; if (!UUID.TryParse(data[1], out master)) continue; if (_masters.Contains(master)) continue; _masters.Add(master); break; } } } catch (FileNotFoundException) { Console.Write("\n[Convey] : Could not read configuration file."); System.Environment.Exit(1); } if (!_masters.Contains(e.IM.FromAgentID)) return; Console.Write("\n[Convey] : Accepting teleport lure."); Client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true); return; } // Ignore group chat. if (e.IM.GroupIM) return; var msg = e.IM.Message.Split(new char[] { ' ' }); if (!msg[0].Equals("auth")) goto NoAuth; try { foreach(string cfg in File.ReadLines("Convey.config")) { string[] data = cfg.Split(new char[] { ':' }, 3); switch (data[0]) { case "secret": if (!data[1].Equals(msg[1])) break; if (!AuthAgents.Contains(e.IM.FromAgentName)) AuthAgents.Add(e.IM.FromAgentName); Console.Write("\n[Convey] : Authenticated: " + e.IM.FromAgentName + "."); break; } } } catch (FileNotFoundException) { Console.Write("\n[Convey] : Could not read configuration file."); System.Environment.Exit(1); } return; NoAuth: if(!AuthAgents.Contains(e.IM.FromAgentName)) goto Command; if (_url.Equals(string.Empty)) return; var im = string.Join(" ", msg); if (im.Equals(string.Empty)) return; new Thread(new ThreadStart(delegate { Console.Write("\n[Convey] : Relaying message: " + im + " to: " + _url + "."); HTTPRequest(_url, "PUT", string.Join(" ", msg)); })).Start(); return; Command: if (!msg[0].Equals("c")) return; // Get the URL. _url = msg[1]; Console.Write("\n[Convey] : Updated URL to: " + _url + "."); HTTPRequest(_url, "POST", "CONVEY->" + _url); } private static HttpWebResponse HTTPRequest(string url, string method, string data) { try { byte[] byteArray = Encoding.UTF8.GetBytes(data); WebRequest request = WebRequest.Create(url); request.Method = method; request.Timeout = 1000; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = byteArray.Length; Stream dataStream = request.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); HttpWebResponse response = (HttpWebResponse) request.GetResponse(); return response; } catch (WebException e) { return (HttpWebResponse) e.Response; } } } }