Package net.bnubot.core

Source Code of net.bnubot.core.Connection

/**
* This file is distributed under the GPL
* $Id: Connection.java 1903 2014-02-12 07:37:25Z scotta $
*/

package net.bnubot.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.bnubot.bot.gui.settings.ConfigurationFrame;
import net.bnubot.bot.gui.settings.OperationCancelledException;
import net.bnubot.bot.gui.wizard.NewProfileWizard;
import net.bnubot.core.bncs.BNCSConnection;
import net.bnubot.core.bncs.ProductIDs;
import net.bnubot.core.botnet.BotNetConnection;
import net.bnubot.db.conf.DatabaseContext;
import net.bnubot.logging.Out;
import net.bnubot.logging.OutputHandler;
import net.bnubot.settings.ConnectionSettings;
import net.bnubot.settings.GlobalSettings;
import net.bnubot.util.BNetInputStream;
import net.bnubot.util.BNetUser;
import net.bnubot.util.ByteArray;
import net.bnubot.util.MirrorSelector;
import net.bnubot.util.TimeFormatter;
import net.bnubot.util.UserProfile;
import net.bnubot.util.Wildcard;
import net.bnubot.util.crypto.GenericCrypto;
import net.bnubot.util.music.MusicController;
import net.bnubot.util.music.MusicControllerFactory;
import net.bnubot.util.task.Task;
import net.bnubot.util.task.TaskManager;
import net.bnubot.vercheck.CurrentVersion;

/**
* @author scotta
*/
public abstract class Connection extends Thread implements OutputHandler {
  public enum ConnectionState {
    DO_NOT_ALLOW_CONNECT,
    LONG_PAUSE_BEFORE_CONNECT,
    ALLOW_CONNECT,
    FORCE_CONNECT,
    CONNECTING,
    CONNECTED;

    public boolean canConnect(long timeWaiting) {
      switch(this) {
      case FORCE_CONNECT:
        return true;
      case ALLOW_CONNECT:
        return GlobalSettings.autoConnect;
      case LONG_PAUSE_BEFORE_CONNECT:
        // Wait 1 minute
        return timeWaiting > (1000l * 60l);
      }
      return false;
    }
  }

  protected Socket socket = null;

  protected ConnectionSettings cs;
  protected Profile profile;
  protected final Collection<EventHandler> eventHandlers = new ArrayList<EventHandler>();
  protected final Collection<BNetUser> users = new LinkedList<BNetUser>();
  protected BNetUser myUser = null;
  protected ConnectionState connectionState = ConnectionState.ALLOW_CONNECT;
  protected String channelName = null;
  protected int channelFlags = 0;
  protected boolean forceReconnect = false;
  protected boolean initialized = false;
  protected boolean disposed = false;

  protected long lastNullPacket;

  protected AcceptOrDecline lastAcceptDecline = null;
  public int enabledCryptos = 0;

  protected Task createTask(String title, String currentStep) {
    Task t = TaskManager.createTask(profile.getName() + ": " + title, currentStep);
    currentTasks.add(t);
    return t;
  }

  protected Task createTask(String title, int max, String units) {
    Task t = TaskManager.createTask(title, max, units);
    currentTasks.add(t);
    return t;
  }

  protected void completeTask(Task t) {
    currentTasks.remove(t);
    t.complete();
  }

  protected void clearTasks() {
    for(Task t : currentTasks)
      t.complete();
    currentTasks.clear();
  }

  /**
   * Create a socket, and if necessary, connect via SOCKS4
   * @throws IOException
   * @throws UnknownHostException
   */
  protected Socket makeSocket(String address, int port) throws UnknownHostException, IOException {
    Socket s;
    if(GlobalSettings.socksEnabled) {
      InetAddress s4_addr = MirrorSelector.getClosestMirror(GlobalSettings.socksHost, GlobalSettings.socksPort);
      int s4_port = GlobalSettings.socksPort;

      switch(GlobalSettings.socksType) {
      case SOCKS4:
        InetAddress addr = MirrorSelector.getClosestMirror(address, port);
        dispatchRecieveInfo("Connecting to " + addr + ":" + port + " via SOCKS4 proxy " + s4_addr + ":" + s4_port + ".");
        s = new SOCKS4ProxySocket(
          s4_addr, s4_port,
          addr, port);
        break;
      case SOCKS4a:
        dispatchRecieveInfo("Connecting to " + address + ":" + port + " via SOCKS4a proxy " + s4_addr + ":" + s4_port + ".");
        s = new SOCKS4ProxySocket(
          s4_addr, s4_port,
          address, port);
        break;
      default:
        throw new IOException("Unknown SOCKS type: " + GlobalSettings.socksType);
      }
    } else {
      InetAddress addr = MirrorSelector.getClosestMirror(address, port);
      dispatchRecieveInfo("Connecting to " + addr + ":" + port + ".");
      s = new Socket(addr, port);
    }
    s.setKeepAlive(true);
    return s;
  }

  protected String getServer() {
    return cs.server;
  }

  protected int getPort() {
    return cs.port;
  }

  private final List<Task> currentTasks = new LinkedList<Task>();
  @Override
  public final void run() {
    // We must initialize the EHs in the Connection thread
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.initialize(this);
      initialized = true;
    }

    while(!disposed) {
      try {
        clearTasks();
        myUser = null;
        dispatchTitleChanged();

        if(!connectionState.canConnect(0)) {
          Out.debug(getClass(), "Pausing to meet connection criteria for " + connectionState.toString());

          // Wait until we're supposed to connect
          long waitStartTime = System.currentTimeMillis();
          while(!connectionState.canConnect(System.currentTimeMillis() - waitStartTime)) {
            yield();
            sleep(200);
          }
        }

        connectionState = ConnectionState.CONNECTING;
        dispatchTitleChanged();
        Task connect = createTask("Connecting to " + getServer() + ":" + getPort(), "Verify connection settings validity");

        if(!cs.isInitialized()) {
          if(PluginManager.getEnableGui()) {
            new NewProfileWizard(cs).displayAndBlock();
          }
        }

        // Check if CS is valid
        while(cs.isValid() != null) {
          if(PluginManager.getEnableGui()) {
            new ConfigurationFrame(cs);
          } else {
            // TODO: handle this somehow
            throw new RuntimeException("Invalid configuration in bot #" + cs.botNum + " " + cs.isValid());
          }
        }

        // Wait a short time before allowing a reconnect
        waitUntilConnectionSafe(connect);
        if(!isConnecting())
          continue;

        // Double-check if disposal occured
        if(disposed)
          break;

        // Initialize connection to DT server
        initializeConnection(connect);
        if(!isConnecting())
          continue;
        connectionState = ConnectionState.CONNECTED;
        dispatchTitleChanged();

        // Log in
        boolean loggedIn = sendLoginPackets(connect);
        if(!isConnected())
          continue;

        // Connection established
        completeTask(connect);

        lastNullPacket = System.currentTimeMillis();
        profile.lastAntiIdle = lastNullPacket;

        if(loggedIn)
          connectedLoop();

        // Connection closed gracefully
        clearTasks();
        // If we're already in a disconnected state, disconnect() has no effect
        disconnect(ConnectionState.ALLOW_CONNECT);
        if(connectionState == ConnectionState.ALLOW_CONNECT) {
          // Disconnect which did not cause an error; wait a minimum of five seconds before reconnecting
          try {
            sleep(5000);
          } catch (Exception e) {}
        }
      } catch(OperationCancelledException e) {
        // Dispose the connection
        disposed = true;
        disconnect(ConnectionState.LONG_PAUSE_BEFORE_CONNECT);
      } catch(UnknownHostException e) {
        dispatchRecieveError("Unknown host: " + e.getMessage());
        disconnect(ConnectionState.LONG_PAUSE_BEFORE_CONNECT);
      } catch(IOException e) {
        dispatchRecieveError(e.getMessage());
        disconnect(ConnectionState.LONG_PAUSE_BEFORE_CONNECT);
      } catch(Exception e) {
        dispatchRecieveError("Unhandled " + e.getClass().getSimpleName() + ": " + e.getMessage());
        e.printStackTrace();
        Out.exception(e);
        disconnect(ConnectionState.LONG_PAUSE_BEFORE_CONNECT);
      }
    }

    clearTasks();

    getProfile().dispose();
  }

  protected abstract void connectedLoop() throws Exception;
  protected abstract boolean sendLoginPackets(Task connect) throws Exception;
  protected abstract void initializeConnection(Task connect) throws Exception;

  private static Map<String, Long> connectionTimes = new HashMap<String, Long>();
  /**
   * Wait until it is safe to connect to the server
   */
  protected void waitUntilConnectionSafe(Task connect) {
    long waitUntil;
    long totalTime;
    String server = getServer();
    synchronized(connectionTimes) {
      long now = System.currentTimeMillis();

      Long lastConnectionTime = connectionTimes.get(server);
      if(lastConnectionTime == null) {
        connectionTimes.put(server, now);
        return;
      }

      waitUntil = lastConnectionTime + 15000;
      totalTime = waitUntil - now;
      connectionTimes.put(server, waitUntil);
    }

    connect.setDeterminate((int)totalTime, "ms");

    final String status = "Stalling to avoid flood";
    while(!disposed) {
      long timeLeft = waitUntil - System.currentTimeMillis();
      if(timeLeft <= 0)
        break;

      if(!connectionState.equals(ConnectionState.CONNECTING))
        break;

      connect.setProgress((int) (totalTime - timeLeft), status);

      try { sleep(100); } catch (InterruptedException e1) {}
      yield();
    }

    connect.setIndeterminate();
  }

  public int getIp() {
    return BNetInputStream.readDWord(socket.getInetAddress().getAddress(), 0);
  }

  private static List<String> antiIdles = new ArrayList<String>();
  protected String getAntiIdle() {
    if(antiIdles.size() == 0) {
      BufferedReader is = null;
      try {
        File f = new File("anti-idle.txt");
        if(!f.exists()) {
          f.createNewFile();

          FileWriter os = new FileWriter(f);
          os.write("# Enter anti-idle messages in this file.\r\n");
          os.write("# \r\n");
          os.write("# Lines beginning with '#' are regarded as comments\r\n");
          os.write("# \r\n");
          os.write("\r\n");
          os.close();
        }
        is = new BufferedReader(new FileReader(f));
      } catch (Exception e) {
        Out.fatalException(e);
      }

      do {
        String line = null;
        try {
          line = is.readLine();
        } catch (IOException e) {
          Out.fatalException(e);
        }
        if(line == null)
          break;

        line = line.trim();
        if(line.length() == 0)
          continue;

        if(line.charAt(0) != '#')
          antiIdles.add(line);
      } while(true);

      try { is.close(); } catch (Exception e) {}
    }

    //grab one
    int i = antiIdles.size();
    if(i == 0)
      return cs.antiIdle;
    i = (int)Math.floor(Math.random() * i);
    return antiIdles.remove(i);
  }

  /**
   * It is imperative that you synchronize on this collection<br>
   * <code>
   * Collection<EventHandler> ehs = [Connection].getEventHandlers();
   * synchronized(ehs) {
   *     ...
   * }
   * </code>
   * @return A synchronized Collection<EventHandler>
   */
  public Collection<EventHandler> getEventHandlers() {
    return eventHandlers;
  }

  public Collection<BNetUser> getUsers() {
    return users;
  }

  public List<BNetUser> getSortedUsers() {
    List<BNetUser> x = new ArrayList<BNetUser>(getUsers());
    ChannelListPriority.sort(x);
    return x;
  }

  private BNetUser getUser(BNetUser u) {
    for(BNetUser user : users)
      if(user.equals(u))
        return user;
    return null;
  }

  private void checkAddUser(BNetUser user) {
    if(getUser(user) == null)
      users.add(user);
  }

  private void removeUser(BNetUser user) {
    if(!users.remove(user))
      Out.error(getClass(), "Tried to remove a user that was not in the list: " + user.toString());
  }

  /**
   * @param containing the string to look for in username
   * @return a list of users according to TC rules
   */
  public List<String> findUsersForTabComplete(String containing) {
    containing = containing.toLowerCase();

    List<String> ret = new ArrayList<String>(users.size());
    for(BNetUser user : getUsers()) {
      String u = user.getShortLogonName();
      if(GlobalSettings.tabCompleteMode.beginsWithMode()) {
        if(u.toLowerCase().startsWith(containing))
          ret.add(u);
      } else {
        if(u.toLowerCase().contains(containing))
          ret.add(u);
      }
    }
    return ret;
  }

  /**
   * @param pattern the wildcard-enabled search string
   * @param perspective whose perspective to search from
   * @return a list of the matched users
   */
  public Collection<BNetUser> findUsersWildcard(String pattern, BNetUser perspective) {
    pattern = pattern.toLowerCase();

    List<BNetUser> ret = new ArrayList<BNetUser>(users.size());
    for(BNetUser user : getUsers()) {
      String u = user.getShortLogonName(perspective).toLowerCase();
      if(Wildcard.matches(pattern, u))
        ret.add(user);
    }
    return ret;
  }

  public Connection(ConnectionSettings cs, Profile p) {
    super(Connection.class.getSimpleName() + "-" + cs.botNum);

    this.cs = cs;
    this.profile = p;

    p.getChatQueue().add(this);
  }

  public String toShortString() {
    if(myUser != null)
      return myUser.getFullLogonName();

    if(cs.isValid() == null)
      return cs.username + "@" + cs.myRealm + " " + connectionState.name();

    return profile.getName();
  }

  public void sendLeaveChat() throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendJoinChannel(String channel) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendJoinChannel2(String channel) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendQueryRealms2() throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendLogonRealmEx(String realmTitle) throws Exception { throw new UnsupportedFeatureException(null); }

  public void sendFriendsList() throws Exception { throw new UnsupportedFeatureException(null); }

  public void sendClanFindCandidates(Object cookie, int clanTag) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendClanInviteMultiple(Object cookie, String clanName, int clanTag, List<String> invitees) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendClanCreationInvitation(int cookie, int clanTag, String inviter, int response) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendClanInvitation(Object cookie, String user) throws Exception { throw new UnsupportedFeatureException(null); }

  public void sendClanRankChange(Object cookie, String user, int newRank) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendClanMOTD(Object cookie) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendClanSetMOTD(String text) throws Exception { throw new UnsupportedFeatureException(null); }

  public void sendReadUserData(String user) throws Exception { throw new UnsupportedFeatureException(null); }
  public void sendWriteUserData(UserProfile profile) throws Exception { throw new UnsupportedFeatureException(null); }

  public void sendProfile(BNetUser user) throws Exception {
    sendReadUserData(user.getFullAccountName());
  }

  public abstract boolean isOp();
  public abstract ProductIDs getProductID();
  public String getDisplayType() {
    return null;
  }
  public abstract String getServerType();

  public void addEventHandler(EventHandler e) {
    synchronized(eventHandlers) {
      eventHandlers.add(e);
      if(initialized)
        e.initialize(this);
    }
  }

  public void removeEventHandler(EventHandler e) {
    synchronized(eventHandlers) {
      e.disable(this);
      eventHandlers.remove(e);
    }
  }

  public boolean isConnected() {
    return (connectionState == ConnectionState.CONNECTED);
  }

  private boolean isConnecting() {
    return (connectionState == ConnectionState.CONNECTING);
  }

  public void connect() {
    if(isConnected() || isConnecting())
      return;

    try {
      if(socket != null) {
        socket.close();
        socket = null;
      }

      String v = cs.isValid();
      if(v != null) {
        dispatchRecieveError(v);
        return;
      }
    } catch(IOException e) {
      Out.fatalException(e);
    }

    connectionState = ConnectionState.FORCE_CONNECT;
    dispatchConnected();
  }

  public void reconnect() {
    disconnect(ConnectionState.FORCE_CONNECT);
  }

  public void disconnect(ConnectionState newState) {
    switch(connectionState) {
    case CONNECTED:
    case CONNECTING:
      // Allow change to any state when in a connected state
      break;
    default:
      // Already disconnected; only accept FORCE_CONNECT (user-initiated)
      switch(newState) {
      case ALLOW_CONNECT:
      case LONG_PAUSE_BEFORE_CONNECT:
        return;
      }
    }

    connectionState = newState;

    try {
      if(socket != null) {
        socket.close();
        socket = null;
        dispatchDisconnected();
      }
    } catch(IOException e) {
      Out.fatalException(e);
    }
  }

  protected long lastChatTime = 0;
  protected int lastChatLen = 0;
  private int increaseDelay(int bytes) {
    long thisTime = System.currentTimeMillis();

    if(lastChatTime == 0) {
      lastChatTime = thisTime;
      lastChatLen = bytes;
      return 0;
    }

    int delay = checkDelay();
    lastChatTime = thisTime;
    lastChatLen = bytes;
    return delay;
  }

  //I found that sending long strings results in flood at values as high as 20; 25 seems safe
  private static final int BYTE_WEIGHT = 25; //15;
  private static final int PACKET_WEIGHT = 2700;
  private int checkDelay() {
    if(lastChatTime == 0)
      return 0;

    long thisTime = System.currentTimeMillis();
    int delay = (lastChatLen * BYTE_WEIGHT) + PACKET_WEIGHT;
    delay -= (thisTime - lastChatTime);
    if(delay < 0)
      return 0;
    return delay;
  }

  public boolean canSendChat() {
    if(channelName == null)
      return false;

    if(myUser == null)
      return false;

    return (checkDelay() <= 0);
  }

  protected void sendChatCommand(ByteArray text) {
    profile.lastAntiIdle = System.currentTimeMillis();

    if(canSendChat())
      increaseDelay(text.length());
    else
      // Fake the bot in to thinking we sent chat in the future
      lastChatTime = System.currentTimeMillis() + increaseDelay(text.length());
  }

  public String cleanText(String text, boolean enableKeywords) {
    //Remove all chars under 0x20
    char[] data = text.toCharArray();
    text = "";
    for(char element : data) {
      if(element >= 0x20)
        text += element;
    }

    boolean somethingDone = true;
    while(somethingDone && enableKeywords) {
      somethingDone = false;

      if(text.indexOf('%') == -1)
        break;

      int i = text.indexOf("%uptime%");
      if(i != -1) {
        somethingDone = true;

        String first = text.substring(0, i);
        String last = text.substring(i + 8);

        long uptime = java.lang.management.ManagementFactory.getRuntimeMXBean().getUptime();
        text = first + TimeFormatter.formatTime(uptime) + last;
      }

      i = text.indexOf("%trigger%");
      if(i != -1) {
        somethingDone = true;

        String first = text.substring(0, i);
        String last = text.substring(i + 9);
        text = first + cs.trigger + last;
      }

      i = text.indexOf("%version%");
      if(i != -1) {
        somethingDone = true;

        String first = text.substring(0, i);
        String last = text.substring(i + 9);
        text = first + CurrentVersion.version() + last;
      }

      i = text.indexOf("%mp3%");
      if(i != -1) {
        String mp3 = null;
        try {
          MusicController musicController = MusicControllerFactory.getMusicController();
          mp3 = musicController.getCurrentlyPlaying();
        } catch(Exception e) {
          e.printStackTrace();
        }

        if(mp3 == null)
          mp3 = "[Music Player Error]";

        somethingDone = true;
        String first = text.substring(0, i);
        String last = text.substring(i + 5);
        text = first + mp3 + last;
      }
    }
    return text;
  }

  /**
   * Helper function to send chat to battle.net
   * @param text The whole text to be sent out
   */
  public void sendChat(String text) {
    sendChat(text, 0);
  }

  /**
   * Helper function to send chat to battle.net
   * @param text The whole text to be sent out
   * @param priority The priority with which to queue the chat
   */
  public void sendChat(String text, int priority) {
    sendChat(null, text, false, true, priority);
  }

  /**
   * Helper function to send chat to battle.net, or parse internal commands
   * such as /cmd, /profile, /botnet, etc
   * @param text The whole text to be sent out
   */
  public void sendChatInternal(String text) {
    sendChat(null, text, true, true, 0);
  }

  /**
   * Helper function to send chat to battle.net
   * @param prefix A String to prepend each time the text is split.
   * <p><code>"/w *BNU-Camel@Azeroth "</code></p>
   * @param text The whole text to be sent out
   * @param allowCommands Enables internal bot commands (/cmd, /profile, etc)
   * @param enableKeywords Enable keywords (%uptime%, %trigger%, %mp3%, etc)
   * @param priority the priority to send to the ChatQueue
   */
  public void sendChat(String prefix, String text, boolean allowCommands, boolean enableKeywords, int priority) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    ALLOWCOMMANDS: if(allowCommands) {
      if(text.charAt(0) == '/') {
        String postSlash = text.substring(1);
        String[] command = postSlash.split(" ", 2);
        if(command[0].length() == 0)
          break ALLOWCOMMANDS;
        switch(command[0].charAt(0)) {
        case '/':
          Profile.internalParseCommand(this, postSlash.substring(1), false);
          return;
        case 'a':
          if(command[0].equals("accept"))
            try {
              lastAcceptDecline.accept();
              lastAcceptDecline = null;
              return;
            } catch(Exception e) {}
          break;
        case 'b':
          if(command[0].equals("b64") || command[0].equals("base64")) {
            enabledCryptos ^= GenericCrypto.CRYPTO_BASE64;
            dispatchRecieveInfo("Base64 crypto " + (((enabledCryptos & GenericCrypto.CRYPTO_BASE64) != 0) ? "enabled" : "disabled"));
            return;
          }
          if(command[0].equals("botnet") && (this instanceof BNCSConnection)) {
            BotNetConnection botnet = ((BNCSConnection)this).getBotNet();
            if(botnet != null) {
              botnet.processCommand(text.substring(8));
              return;
            }
          }
          break;
        case 'c':
          if(command[0].equals("cmd")) {
            if(command.length == 2) {
              Profile.internalParseCommand(this, command[1], true);
              return;
            }
          }
          break;
        case 'd':
          if(command[0].equals("decline"))
            try {
              lastAcceptDecline.decline();
              lastAcceptDecline = null;
              return;
            } catch(Exception e) {}
          if(command[0].equals("dm")) {
            enabledCryptos ^= GenericCrypto.CRYPTO_DM;
            dispatchRecieveInfo("DM crypto " + (((enabledCryptos & GenericCrypto.CRYPTO_DM) != 0) ? "enabled" : "disabled"));
            return;
          }
          break;
        case 'h':
          if(command[0].equals("hex")) {
            enabledCryptos ^= GenericCrypto.CRYPTO_HEX;
            dispatchRecieveInfo("Hex crypto " + (((enabledCryptos & GenericCrypto.CRYPTO_HEX) != 0) ? "enabled" : "disabled"));
            return;
          }
          break;
        case 'j':
          try {
            if(command[0].equals("j")) {
              sendJoinChannel(command[1]);
              return;
            }
            if(command[0].equals("join")) {
              sendJoinChannel(command[1]);
              return;
            }
            if(command[0].equals("join2")) {
              sendJoinChannel2(command[1]);
              return;
            }
          } catch(Exception e) {
            Out.exception(e);
            break ALLOWCOMMANDS;
          }
          break;
        case 'm':
          if(command[0].equals("mc")) {
            enabledCryptos ^= GenericCrypto.CRYPTO_MC;
            dispatchRecieveInfo("MC crypto " + (((enabledCryptos & GenericCrypto.CRYPTO_MC) != 0) ? "enabled" : "disabled"));
            return;
          }
          break;
        case 'p':
          if(command[0].equals("profile")) {
            try {
              if((command.length < 2) || (command[1].length() == 0))
                sendProfile(myUser);
              else
                sendReadUserData(command[1]);
            } catch(Exception e) {
              Out.exception(e);
            }
            return;
          }
          break;
        case 'q':
          if(command[0].equals("quote")) {
            sendChat(prefix, command[1], false, false, priority);
            return;
          }
          break;
        case 'r':
          if(command[0].equals("reverse")) {
            enabledCryptos ^= GenericCrypto.CRYPTO_REVERSE;
            dispatchRecieveInfo("Reverse crypto " + (((enabledCryptos & GenericCrypto.CRYPTO_REVERSE) != 0) ? "enabled" : "disabled"));
            return;
          }
          break;
        case 'w':
          if(command[0].equals("whoami"))
            break ALLOWCOMMANDS;
          if(command[0].equals("whois"))
            break ALLOWCOMMANDS;
          break;
        default:
        }

        if(postSlash.length() > 0) {
          if(Profile.internalParseCommand(this, postSlash, true))
            return;
        }
      }
    } // if(allowCommands)

    if(!isConnected()) {
      dispatchRecieveError("Can not send chat: " + text);
      return;
    }

    text = cleanText(text, enableKeywords);

    if(prefix == null) {
      // Detect /w user and /f m, and make that the prefix
      if(text.startsWith("/w ")) {
        int i = text.indexOf(' ', 4) + 1;
        if(i != 0) {
          prefix = text.substring(0, i);
          text = text.substring(i);
        }
      } else if(text.startsWith("/f m ")) {
        prefix = "/f m ";
        text = text.substring(5);
      }
    }

    if(text.length() == 0)
      return;

    ByteArray baPrefix = null;
    if(prefix != null)
      baPrefix = new ByteArray(prefix);
    enqueueChat(baPrefix, text, priority);
  }

  public static final int MAX_CHAT_LENGTH = 200;
  /**
   * @param prefix Prepend this before each line of text.
   * <p><code>new ByteArray("/w *BNU-Camel@Azeroth ")</code></p>
   * @param text The string to split up and send; unicode is okay.
   * <p><code>"Hello!"</code></p>
   * @param priority
   */
  private void enqueueChat(ByteArray prefix, String text, int priority) {
    //Split up the text in to appropriate sized pieces
    int pieceSize = MAX_CHAT_LENGTH;
    if(prefix != null)
      pieceSize -= prefix.length();
    if(enabledCryptos != 0) {
      if((enabledCryptos & GenericCrypto.CRYPTO_REVERSE) != 0)
        pieceSize--; // Reverse has a prefix
      if((enabledCryptos & GenericCrypto.CRYPTO_MC) != 0)
        pieceSize--; // MC has a prefix
      if((enabledCryptos & GenericCrypto.CRYPTO_DM) != 0)
        pieceSize = (pieceSize - 1) / 2; // DM doubles in size and has a prefix
      if((enabledCryptos & GenericCrypto.CRYPTO_HEX) != 0)
        pieceSize = (pieceSize - 1) / 2; // Hex doubles in size and has a prefix
      if((enabledCryptos & GenericCrypto.CRYPTO_BASE64) != 0)
        pieceSize = (pieceSize - 1) * 3 / 4; // B64 increases 33% and has a prefix
    }

    enqueueChat(prefix, text, priority, pieceSize, 0);
  }

  /**
   * @param prefix Prepend this before each line of text.
   * <p><code>new ByteArray("/w *BNU-Camel@Azeroth ")</code></p>
   * @param text The string to split up and send; unicode is okay.
   * <p><code>"Hello!"</code></p>
   * @param priority
   * @param pieceSize
   * @param cq
   * @param current_position
   */
  private void enqueueChat(ByteArray prefix, String text, int priority, int pieceSize, int current_position) {
    // Count the number of unicode characters removed from text
    int chars_pulled = pieceSize;
    // Count the number of characters remaining from the current position
    int chars_left = text.length() - current_position;
    if(chars_pulled > chars_left)
      chars_pulled = chars_left;

    // Determine whether to prepend ellipsies
    boolean firstPiece = (current_position == 0);

    // This is the pieceSize accomadating for the unprepended ellipsis
    int maxByteCount = (firstPiece ? pieceSize : pieceSize - 3);

    ByteArray piece = new ByteArray(text.substring(current_position, current_position + chars_pulled));
    while(piece.length() > maxByteCount) {
      // Estimate that we need to remove half the overflow, since most unicode chars are 2-bytes
      int deltaEstimate = (piece.length() - pieceSize + 1) / 2;
      // Don't remove less than 1 character
      if(deltaEstimate < 1)
        deltaEstimate = 1;
      Out.debug(getClass(), "deltaEstimate=" + deltaEstimate);
      chars_pulled -= deltaEstimate;
      piece = new ByteArray(text.substring(current_position, current_position + chars_pulled));
    }

    boolean lastPiece = (current_position + chars_pulled >= text.length());
    if(!lastPiece) {
      // This is not the last piece; append ellipsis
      chars_pulled -= 3;
      piece = new ByteArray(text.substring(current_position, current_position + chars_pulled)).concat("...".getBytes());
    }

    if(!firstPiece) {
      // This is not the first piece; prepend ellipsis
      piece = new ByteArray("...".getBytes()).concat(piece);
    }

    // Cryptos
    if(enabledCryptos != 0)
      piece = GenericCrypto.encode(piece, enabledCryptos);

    // Prepend the prefix
    if(prefix != null)
      piece = prefix.concat(piece);

    profile.getChatQueue().enqueue(this, piece, priority);
    if(!lastPiece)
      enqueueChat(prefix, text, priority, pieceSize, current_position + chars_pulled);
  }

  public ConnectionSettings getConnectionSettings() {
    return cs;
  }

  public BNetUser getMyUser() {
    return myUser;
  }

  public Profile getProfile() {
    return profile;
  }

  public boolean isInitialized() {
    return initialized;
  }

  /**
   * Destroy and cleanup this Connection
   */
  public void dispose() {
    disposed = true;
    disconnect(ConnectionState.DO_NOT_ALLOW_CONNECT);

    synchronized(eventHandlers) {
      for(EventHandler e : new ArrayList<EventHandler>(eventHandlers)) {
        eventHandlers.remove(e);
        e.disable(this);
      }
    }
  }

  /**
   * Determine if this Connection is the primary for a Profile
   * @return true if the connection is primary
   */
  protected boolean isPrimaryConnection() {
    return equals(profile.getPrimaryConnection());
  }

  /**
   * Get the channel this Connection is in
   * @return The channel
   */
  public String getChannel() {
    return channelName;
  }

  public char getTrigger() {
    return getConnectionSettings().trigger.charAt(0);
  }

  /**
   * Look for a BNetUser in the user list.
   * @param user "User[#N][@Realm]"
   * @param perspective The BNetUser whose realm to use if none is specified
   * @return A BNetUser representing the user, or null if none is found
   */
  public BNetUser findUser(String user, BNetUser perspective) {
    if(user.indexOf('@') == -1)
      user += '@' + perspective.getRealm();
    for(BNetUser u : users)
      if(u.equals(user))
        return u;
    return null;
  }

  /**
   * Look for a BNetUser in the user list. If it doesn't exist, create a new one
   * @param user "User[#N][@Realm]"
   * @param perspective The BNetUser whose realm to use if none is specified
   * @return A BNetUser representing the user
   */
  public BNetUser getCreateBNetUser(String user, BNetUser perspective) {
    BNetUser x = findUser(user, perspective);
    if(x != null)
      return x;
    return new BNetUser(this, user, perspective);
  }

  /**
   * Clear all text waiting to send in the ChatQueue
   */
  public void clearQueue() {
    profile.getChatQueue().clear();
  }

  /*
   * EventHandler methods follow
   *
   */
  protected void dispatchConnected() {
    users.clear();

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.bnetConnected(this);
    }
  }

  protected void dispatchDisconnected() {
    channelName = null;
    channelFlags = 0;
    users.clear();
    myUser = null;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.bnetDisconnected(this);
    }
  }

  protected void dispatchEnterChat(BNetUser user) {
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.enterChat(this, user);
    }
  }

  private void dispatchParseCommand(BNetUser user, String command, boolean whisperBack) {
    if(DatabaseContext.getContext() == null)
      return;

    int i = command.indexOf(';');
    if(i != -1) {
      String c1 = command.substring(0, i);
      String c2 = command.substring(i + 1);
      while((c2.length() > 0) && (c2.charAt(0) == ' '))
        c2 = c2.substring(1);

      Profile.parseCommand(this, user, c1, whisperBack);
      dispatchParseCommand(user, c2, whisperBack);
      return;
    }

    Profile.parseCommand(this, user, command, whisperBack);
  }

  protected void dispatchTitleChanged() {
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.titleChanged(this);
    }
  }

  protected void dispatchJoinedChannel(String channel, int flags) {
    if(!isPrimaryConnection())
      return;

    channelName = channel;
    channelFlags = flags;
    users.clear();

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.joinedChannel(this, channel);
    }

    for(Connection c : profile.getConnections()) {
      if(equals(c))
        continue;
      if(c instanceof BotNetConnection)
        continue;
      try {
        if(c.myUser != null) {
          Out.info(getClass(), "[" + myUser.getFullLogonName() + "] Telling [" + c.myUser.getFullLogonName() + "] to join " + channel);
          c.sendJoinChannel(channel);
        }
      } catch (Exception e) {
        Out.exception(e);
      }
    }
  }

  protected void dispatchChannelUser(BNetUser user) {
    checkAddUser(user);

    if(!isPrimaryConnection())
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.channelUser(this, user);
    }
  }

  protected void dispatchChannelJoin(BNetUser user) {
    checkAddUser(user);

    if(!isPrimaryConnection())
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.channelJoin(this, user);
    }
  }

  protected void dispatchChannelLeave(BNetUser user) {
    removeUser(user);

    if(!isPrimaryConnection())
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.channelLeave(this, user);
    }
  }

  protected void dispatchRecieveChat(BNetUser user, ByteArray data) {
    if(!isPrimaryConnection())
      return;

    // Try to remove encryption
    try {
      data = GenericCrypto.decode(data);
    } catch(Exception e) {
      Out.exception(e);
    }

    // Convert to string
    String text = data.toString();
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveChat(this, user, text);
    }

    if(text.length() == 0)
      return;

    if((text.charAt(0) == getTrigger()) || text.equalsIgnoreCase("?trigger"))
      dispatchParseCommand(user, text.substring(1), GlobalSettings.whisperBack);
  }

  protected void dispatchRecieveBroadcast(String username, int flags, String text) {
    if(!isPrimaryConnection())
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveBroadcast(this, username, flags, text);
    }
  }

  protected void dispatchRecieveEmote(BNetUser user, String text) {
    if(!isPrimaryConnection())
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveEmote(this, user, text);
    }
  }

  @Override
  public void dispatchRecieveDebug(String text) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveDebug(this, text);
    }
  }

  public void dispatchRecieveServerInfo(String text) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveServerInfo(this, text);
    }

    if(GlobalSettings.autoRejoin
    && (myUser != null)
    && text.startsWith(myUser.getShortLogonName() + " was kicked out of the channel")) {
      final String oldChannel = channelName;
      dispatchRecieveError("Will auto-rejoin in 5 seconds...");
      new Thread() {
        @Override
        public void run() {
          try {
            sleep(5000);
            if(channelName.equalsIgnoreCase("The Void"))
              sendJoinChannel(oldChannel);
            else
              dispatchRecieveError("Auto-rejoin cancelled.");
          } catch(Exception e) {
            Out.exception(e);
          }
        }}.start();
    }
  }

  @Override
  public void dispatchRecieveInfo(String text) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveInfo(this, text);
    }
  }

  public void dispatchRecieveServerError(String text) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveServerError(this, text);
    }
  }

  @Override
  public void dispatchRecieveError(String text) {
    if(text == null)
      return;
    if(text.length() == 0)
      return;

    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.recieveError(this, text);
    }
  }

  protected void dispatchWhisperSent(BNetUser user, String text) {
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.whisperSent(this, user, text);
    }
  }

  protected void dispatchWhisperRecieved(BNetUser user, String text) {
    synchronized(eventHandlers) {
      for(EventHandler eh : eventHandlers)
        eh.whisperRecieved(this, user, text);
    }

    if((text == null) || (text.length() == 0))
      return;
    if(text.charAt(0) == getTrigger())
      text = text.substring(1);
    dispatchParseCommand(user, text, true);
  }
}
TOP

Related Classes of net.bnubot.core.Connection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.