Table of Contents

Shortnote

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:

Compiling the Bot

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.

Code

Convey.cs
///////////////////////////////////////////////////////////////////////////
//  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;
      }
    }
  }
}