net.jmhertlein.alphonseirc.AlphonseBot.java Source code

Java tutorial

Introduction

Here is the source code for net.jmhertlein.alphonseirc.AlphonseBot.java

Source

/*
 * Copyright (C) 2013-14 Joshua Michael Hertlein <jmhertlein@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.jmhertlein.alphonseirc;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.PircBot;

/**
 *
 * @author Joshua Michael Hertlein <jmhertlein@gmail.com>
 */
public class AlphonseBot extends PircBot {

    private static final String INTERJECTION = "I'd just like to interject for moment. What you're refering to as $WORD, is in fact, GNU/$WORD, or as I've recently taken to calling it, GNU plus $WORD. $WORD is not a word unto itself, but rather another free component of a fully functioning GNU word made useful by the GNU letters, vowels and vital verbiage components comprising a full word as defined by POSIX. ";
    private Random gen;
    private final String nick, pass, server;
    private final List<String> channels;
    private final LinkedList<String> previousSenders;
    private int maxXKCD;
    private boolean voicing;
    private TTTGame ttt;
    private final Map<LocalDate, LocalTime> dadLeaveTimes;

    private final Set<String> noVoiceNicks, masters;

    public AlphonseBot(String nick, String pass, String server, List<String> channels, int maxXKCD,
            Set<String> noVoiceNicks, Set<String> masters, Map<LocalDate, LocalTime> dadLeaveTimes) {
        this.pass = pass;
        this.nick = nick;
        this.channels = channels;
        this.server = server;
        setName(nick);

        this.maxXKCD = maxXKCD;
        this.noVoiceNicks = noVoiceNicks;
        this.masters = masters;
        voicing = true;

        this.dadLeaveTimes = dadLeaveTimes;

        previousSenders = new LinkedList<>();
    }

    public void startConnection() throws IOException, IrcException {
        System.out.println("Joining " + server);
        connect(server);
        System.out.println("Joined " + server);
        for (String chan : channels) {
            System.out.println("Joining " + chan);
            joinChannel(chan);
            System.out.println("Joined " + chan);
        }
        gen = new Random();
    }

    @Override
    protected void onJoin(String channel, String sender, String login, String hostname) {
        if (sender.equals(nick)) {
            return;
        }

        if (voicing && !noVoiceNicks.contains(sender)) {
            voice(channel, sender);
        }

        System.out.println("Voiced " + sender);
        System.out.println(sender + " joined.");
    }

    @Override
    protected void onConnect() {
        if (!pass.isEmpty()) {
            this.identify(pass);
            System.out.println("Messaged NickServ");
        } else {
            System.out.println("Empty pass, skipping nickserv identification.");
        }
    }

    @Override
    protected void onMessage(String channel, String sender, String login, String hostname, String message) {
        if (previousSenders.size() > 200) {
            previousSenders.removeFirst();
        }
        previousSenders.add(sender);

        if (message.equalsIgnoreCase(String.format("%s: help", nick))) {
            sendMessage(sender, "Master, I am capable of many things. I shall list them:");
            sendMessage(sender, "Lord Munroe is truly witty- I can fetch you one of his drole comics. (!xkcd)");
            sendMessage(sender, "I am well-versed in the art of converting phrases to numbers. (!hash)");
            sendMessage(sender,
                    "Lord Stallman's logic is undeniable. I can correct the verbiage of the unenlightened with His GNU+Wisdom. (!interject)");
            sendMessage(sender, "I am also well-phrased in the art of turning phrases into letters. (!encode)");
            sendMessage(sender, "I can also turn them back into phrases. (!decode)");
            sendMessage(sender,
                    "And sir, should you wish me to keep your orders private, simply /msg me them and I will report them to you confidentially.");
        }

        if (message.startsWith("!")) {
            onCommand(channel, sender, message.substring(1).split(" "));
        }
    }

    private void sendXKCD(String channel, String sender) {
        if (MSTDeskEngRunner.checkXKCDUpdate()) {
            this.maxXKCD = MSTDeskEngRunner.getMaxXKCD();
            MSTDeskEngRunner.writeConfig();
        }

        sendMessage(channel, sender + ": Your XKCD is ready, sir: https://xkcd.com/" + (1 + gen.nextInt(maxXKCD)));
    }

    private void interject(String target, String[] args) {
        String name, word;
        if (args.length != 3) {
            sendMessage(target, "Incorrect number of args. Usage: !interject <GNU+target> <GNU+word>");
            return;
        }
        name = args[1];
        word = args[2];

        sendMessage(target, name + ": " + INTERJECTION.replace("$WORD", word));
    }

    @Override
    protected void onPrivateMessage(String sender, String login, String hostname, String message) {
        System.out.printf("[PM][%s (Login: %s)]: %s\n", sender, login, message);

        if (message.startsWith("!")) {
            onCommand(sender, sender, message.substring(1).split(" "));
        }
    }

    @Override
    protected void onPart(String channel, String sender, String login, String hostname) {
        System.out.println(sender + " parted.");
    }

    @Override
    protected void onKick(String channel, String kickerNick, String kickerLogin, String kickerHostname,
            String recipientNick, String reason) {
        if (recipientNick.equals(nick)) {
            System.out.println(kickerNick + " kicked me! Rejoining...");
            joinChannel(channel);
        }
    }

    @Override
    protected void onDisconnect() {
        System.err.println("I got disconnected!");
        System.out.println("I got disconnected!");

        int attempt = 0;
        boolean error = false;
        while (!isConnected() || error) {
            try {
                System.out.printf("Trying to reconnect... (Attempt %s)\n", attempt);
                connect(server);
                System.out.println("Reconnected.");
                error = false;
            } catch (IOException | IrcException ex) {
                System.err.println("Error reconnecting to " + server);
                error = true;
            }
        }

        System.out.println("Rejoining channels...");
        for (String chan : channels) {
            System.out.println("Rejoining " + chan);
            joinChannel(chan);
            System.out.println("Rejoined " + chan);
        }
        System.out.println("Rejoining finished.");
    }

    public void onPreQuit() {
        for (String s : channels) {
            partChannel(s, "Tried to dereference 0x00000000");
        }
    }

    private void onCommand(String target, String sender, String[] args) {
        String cmd = args[0];
        String rest = "";
        for (int i = 1; i < args.length; i++) {
            rest += args[i] + " ";
        }
        rest = rest.trim();

        switch (cmd) {
        case "voice":
            if (masters.contains(sender)) {
                handleVoice(target, args, true);
            }
            break;
        case "novoice":
            if (masters.contains(sender)) {
                handleVoice(target, args, false);
            }
            break;
        case "stopvoice":
            if (masters.contains(sender)) {
                voicing = false;
                sendMessage(target, "Stopping voicing.");
            }
            break;
        case "startvoice":
            if (masters.contains(sender)) {
                voicing = true;
                sendMessage(target, "Resuming voicing.");
            }
            break;
        case "xkcd":
            sendXKCD(target, sender);
            break;
        case "interject":
            interject(target, args);
            break;
        case "hash":
            handleHashCommand(target, sender, args, cmd, rest);
            break;
        case "encode":
            sendMessage(target, sender + ": " + Base64.encodeBase64String(rest.getBytes()));
            break;
        case "decode":
            sendMessage(target, sender + ": " + new String(Base64.decodeBase64(rest)));
            break;
        case "permute":
            handlePermuteCommand(sender, target, args);
            break;
        case "billy":
            handleBillyCommand(target);
            break;
        case "tt":
        case "ttt":
            handleTTTCommand(target, sender, args);
            break;
        case "dad":
            handleDadCommand(target, sender, args);
            break;
        }
    }

    private void handleBillyCommand(String target) {
        sendMessage(target, "Measuring Billium levels...");
        int total = previousSenders.size(), billy = 0;
        for (String previousSender : previousSenders) {
            if (previousSender.equals("brodes")) {
                billy++;
            }
        }
        if (total > 0) {
            BigDecimal conc = new BigDecimal(((float) billy) / total * 100);
            sendMessage(target, "Current Billium concentration: " + conc.toPlainString() + "%");
            String status;
            if (conc.compareTo(new BigDecimal(80)) > 0) {
                status = "!!DANGER!! OVERDOSE IMMENENT";
            } else if (conc.compareTo(new BigDecimal(50)) > 0) {
                status = "WARNING - DANGEROUS LEVELS";
            } else if (conc.compareTo(new BigDecimal(30)) > 0) {
                status = "Caution - Levels rising, but stable";
            } else {
                status = "Billium levels negligible.";
            }
            sendMessage(target, "Current status: " + status);
        } else {
            sendMessage(target, "Billium levels negligible.");
        }
    }

    private void handlePermuteCommand(String sender, String target, String[] args) {
        if (!sender.equals("Everdras")) {
            sendMessage(target, sender + " pls go");
            return;
        }
        if (args.length != 2) {
            sendMessage(target, "Incorrect number of args. Usage: !permute <string>");
            return;
        }

        String word = args[1];
        if (word.length() > 5) {
            sendMessage(target, "Too long, max length: 5");
            return;
        }
        sendMessage(target, "Permuting...");
        List<String> perms = permute(word);
        sendMessage(target, "Permutations: " + perms.size());
        long origDelay = getMessageDelay();
        this.setMessageDelay(10);
        List<String> output = new LinkedList<>();
        StringBuilder b = new StringBuilder();
        int c = 0;
        for (String s : perms) {
            b.append(s);
            b.append(' ');
            c++;
            if (c == 8) {
                c = 0;
                output.add(b.toString());
                b = new StringBuilder();
            }
        }
        if (b.length() != 0) {
            output.add(b.toString());
        }
        for (String s : output) {
            sendMessage(target, s);
        }
        this.setMessageDelay(origDelay);
    }

    public static List<String> permute(String s) {
        List<String> ret = new LinkedList<>();
        if (s.isEmpty()) {
            ret.add("");
            return ret;
        }

        for (char c : s.toCharArray()) {
            for (String subPermute : permute(s.replaceFirst("[" + c + "]", ""))) {
                ret.add(c + subPermute);
            }
        }
        return ret;
    }

    private void handleHashCommand(String target, String sender, String[] args, String cmd, String rest) {
        sendMessage(target, "hashcode() of \"" + rest + "\": " + rest.hashCode());
    }

    private void handleVoice(String target, String[] args, boolean voiced) {
        if (args.length != 2) {
            sendMessage(target, "Incorrect number of args. Usage: !novoice <nick>");
            return;
        }
        String targetNick = args[1];

        if (voiced) {
            noVoiceNicks.remove(targetNick);
            voice(target, targetNick);
            sendMessage(target, "Voiced " + targetNick);
        } else {
            noVoiceNicks.add(targetNick);
            deVoice(target, targetNick);
            sendMessage(target, "Devoiced " + targetNick);
        }

        MSTDeskEngRunner.writeConfig();
    }

    private void handleTTTCommand(String target, String sender, String[] args) {
        String cmd;
        if (args.length > 1) {
            cmd = args[1];
        } else {
            sendMessage(target, "Incorrect number of args. Usage: !ttt [start|kill|mv]");
            return;
        }

        switch (cmd) {
        case "start":
            if (args.length != 3) {
                sendMessage(target, "Incorrect number of args. Usage: !ttt start [otherPlayer]");
                break;
            }

            if (ttt != null) {
                sendMessage(target, "Game already in progress.");
                break;
            }
            String x, o;
            if (gen.nextBoolean()) {
                x = sender;
                o = args[2];
            } else {
                x = args[2];
                o = sender;
            }

            ttt = new TTTGame(x, o);
            sendMessage(target, "X player is: " + x);
            sendMessage(target, "O player is: " + o);
            sendMessage(target, "X goes first.");
            long original = getMessageDelay();
            setMessageDelay(10);
            for (String s : ttt.printBoard()) {
                sendMessage(target, s);
            }
            setMessageDelay(original);
            break;
        case "kill":
            if (ttt == null) {
                sendMessage(target, "No game to kill.");
                break;
            }
            if (masters.contains(sender) || ttt.isParticipating(nick)) {
                ttt = null;
                sendMessage(target, "Game killed.");
            } else {
                sendMessage(target, "Insufficient permission to kill game.");
                break;
            }
            break;
        case "move":
        case "mv":
            int[] dest = handleDestProcessing(target, args);

            if (dest == null)
                return;

            int i = dest[0], j = dest[1];

            if (!ttt.isValidMove(i, j)) {
                sendMessage(target, "Invalid move.");
                break;
            }

            if (ttt.moveForPlayer(sender, i, j)) {
                sendMessage(target, "Moved.");
                long orig = getMessageDelay();
                this.setMessageDelay(10);
                for (String s : ttt.printBoard()) {
                    sendMessage(target, s);
                }
                setMessageDelay(orig);
                ttt.flipTurn();
            } else {
                sendMessage(target, sender + ": You're either not in this game, or it's not your turn.");
                break;
            }

            if (ttt.isWon()) {
                sendMessage(target, ttt.getWinner() + " has won.");
                ttt = null;
            }
            break;
        case "print":
            if (ttt == null) {
                sendMessage(target, "No game in session.");
                break;
            } else {
                for (String s : ttt.printBoard()) {
                    sendMessage(target, s);
                }
                sendMessage(target, "It is " + (ttt.isXTurn() ? "X" : "O") + "'s turn.");
            }
            break;
        default:
            sendMessage(target, "Unknown sub-command of !ttt. Usage: !ttt start [otherPlayer]");
            break;
        }

    }

    private int[] handleDestProcessing(String target, String[] args) {
        if (args.length == 4) {
            String xPos = args[2], yPos = args[3];
            int i, j;
            try {
                i = Integer.parseInt(xPos);
                if (i < 0 || i > 2) {
                    throw new NumberFormatException();
                }
            } catch (NumberFormatException nfe) {
                sendMessage(target,
                        String.format("Invalid integer \"%s\", should be int in range [0,2] inclusive.", xPos));
                return null;
            }
            try {
                j = Integer.parseInt(yPos);
                if (j < 0 || j > 2) {
                    throw new NumberFormatException();
                }
            } catch (NumberFormatException nfe) {
                sendMessage(target,
                        String.format("Invalid integer \"%s\", should be int in range [0,2] inclusive.", yPos));
                return null;
            }

            return new int[] { i, j };
        } else if (args.length == 3) {
            if (args[2].length() != 1) {
                sendMessage(target, "Label should be a single character between A and I, inclusive.");
                return null;
            }

            char input = args[2].toUpperCase().charAt(0);
            if (input < 'A' || input > 'I') {
                sendMessage(target, "Label should be a single character between A and I, inclusive.");
                return null;
            }

            return TTTGame.decode(input);
        } else {
            sendMessage(target,
                    "Incorrect number of args. Usage: !ttt mv (<x> <y>|<label>)  where (0,0) is the top-left");
            return null;
        }
    }

    private void handleDadCommand(final String target, String sender, String[] args) {
        if (args.length == 1) {
            sendMessage(target, "Usage: !dad [left|list|say]");
            return;
        }

        switch (args[1]) {
        case "left":
            ZonedDateTime now = ZonedDateTime.now();
            this.dadLeaveTimes.put(LocalDate.now(), LocalTime.now());
            sendMessage(target,
                    "Marked dad's leave time as now (" + now.format(DateTimeFormatter.ISO_LOCAL_TIME) + ").");
            break;
        case "list":
            int num = 3;
            if (args.length == 3) {
                try {
                    num = Integer.parseInt(args[2]);
                    if (num > 10)
                        num = 10;
                } catch (NumberFormatException nfe) {
                    sendMessage(target, "Error parsing " + args[2] + " into int: " + nfe.getMessage());
                    sendMessage(target, "Usage: !dad list (optional: number, default 3, max 10)");
                    return;
                }
            }

            final int prevDays = num;
            dadLeaveTimes.keySet().stream().sorted()
                    .filter(date -> date.isAfter(LocalDate.now().minusDays(prevDays)))
                    .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE) + " || "
                            + dadLeaveTimes.get(date).format(DateTimeFormatter.ISO_LOCAL_TIME))
                    .forEach(leaveTime -> sendMessage(target, leaveTime));

            break;
        case "say":
            String msg;
            switch (gen.nextInt(9)) {
            case 0:
                msg = "TYPES LOUDLY";
                break;
            case 1:
                msg = "BREATHES DEEPLY";
                break;
            case 2:
                msg = "ANGRILY TYPES AN EMAIL";
                break;
            case 3:
                msg = "BACKSPACES WITH AUTHORITY";
                break;
            case 4:
                msg = "STRETCHES WHILE EXHALING";
                break;
            case 5:
                msg = "thinks about Happy Hour";
                break;
            case 6:
                msg = "browses Yahoo! news";
                break;
            case 7:
                msg = "tells everyone to GET BACK TO WORK";
                break;
            case 8:
                msg = "ignores Lantzer standing in front of his desk";
                break;
            default:
                msg = "Someone made nextInt() go too high";
                break;
            }

            this.sendAction(target, msg);
            break;
        default:
            System.out.println("Bad switch on " + args[1]);
        }

    }
}