4
\$\begingroup\$

This is my Main class, all it does is start the bot object.

package com.k35trel.twitchbot;

public class Main
{

    private static final String HOST = "xxxxx";
    private static final int PORT = xxxxx;
    private static final String PASS = "oauth:xxxxx";
    private static final String IDENT = "xxxxx";
    private static final String CHANNEL = "xxxxx";

    public static void main(String[] args)
    {
        Bot twitchBot = new Bot(IDENT, HOST, PASS, CHANNEL, PORT);

        twitchBot.start();
    }
}

The Bot class contains my main loop and creates a chat object.

package com.k35trel.twitchbot;

import java.io.*;
import java.net.Socket;

public class Bot
{
    private String IDENT;
    private String HOST;
    private String PASS;
    private String CHANNEL;
    private int PORT;

    private Socket s;

    private BufferedWriter bw;
    private BufferedReader br;
    private Chat chat;

    private boolean isRunning = false;

    public Bot(String IDENT, String HOST, String PASS, String CHANNEL, int PORT)
    {
        this.IDENT = IDENT.toLowerCase();
        this.HOST = HOST.toLowerCase();
        this.PASS = PASS.toLowerCase();
        this.CHANNEL = CHANNEL.toLowerCase();
        this.PORT = PORT;
    }

    private void init()
    {
        System.out.println("Version 6");

        try
        {
            s = new Socket(HOST, PORT);

            bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            chat = new Chat(IDENT, CHANNEL);

            chat.sendToServer(bw, "PASS " + PASS);
            chat.sendToServer(bw, "NICK " + IDENT);
            chat.sendToServer(bw, "USER " + IDENT);
            chat.sendToServer(bw, "CAP REQ :twitch.tv/membership");
            chat.sendToServer(bw, "CAP REQ :twitch.tv/commands");
            chat.sendToServer(bw, "JOIN #" + CHANNEL);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    private void loop()
    {   
        isRunning = true;
        String line = "";

        try
        {
            while((line = br.readLine()) != null && isRunning)
            {
                if(line.contains("PRIVMSG"))
                {
                    String user = line.substring(1, line.indexOf("!"));
                    String message = line.substring(line.indexOf(" :") + 2);

                    System.out.println(user + " >> " + message);

                    int command = chat.processCommands(bw, user, message);
                    executeCommand(command);

                }
                else if(line.contains("PING"))
                {
                    chat.sendToServer(bw, "PONG :tmi.twitch.tv");
                }
                else if(line.contains("JOIN"))
                {
                    String user = line.substring(1, line.indexOf("!"));

                    System.out.println(user + " has joined " + CHANNEL + "'s Channel");
                }
                else if(line.contains("PART"))
                {
                    String user = line.substring(1, line.indexOf("!"));

                    System.out.println(user + " has left " + CHANNEL + "'s Channel");
                }
                else if(line.contains("WHISPER"))
                {
                    String user = line.substring(1, line.indexOf("!"));
                    String message = line.substring(line.indexOf(" :") + 2);

                    System.out.println(user + " ~~ " + message);
                }

                if(isRunning == false)
                {
                    chat.sendToServer(bw, "PING :tmi.twitch.tv");
                }
            }
            chat.sendToServer(bw, "PART #" + CHANNEL);
            s.close();
            bw.close();
            br.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    private void executeCommand(int command)
    {
        if(command == -1)
        {
            System.out.println("shutting down");
            isRunning = false;
        }
    }

    public void start()
    {
        if(isRunning)
        {
            return;
        }

        init();
        loop();
    }
}

The Chat class contains methods for sending data to the server, and for processing commands sent by people in chat.

package com.k35trel.twitchbot;

import java.io.BufferedWriter;
import java.io.IOException;

public class Chat
{
    private String IDENT;
    private String CHANNEL;

    public Chat(String IDENT, String CHANNEL)
    {
        this.IDENT = IDENT;
        this.CHANNEL = CHANNEL;
    }

    public int processCommands(BufferedWriter bw, String user, String message)
    {
        String[] command = message.split(" ");

        //example commands.

        if(command[0].equals("!shutdown") && user.equals(CHANNEL))
        {
            return -1;
        }
        else if(command[0].equals("!ping"))
        {
            sendToChat(bw, "@" + user + " pong!");
            return 0;
        }
        else
        {
            return 0;
        }
    }

    public void sendToServer(BufferedWriter bw, String message)
    {
        try
        {
            bw.write(message + "\r\n");
            bw.flush();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public void sendToChat(BufferedWriter bw, String message)
    {
        sendToServer(bw, "PRIVMSG #" + CHANNEL + " :" + message);
        System.out.println(IDENT + " >> " + message);
    }

    public void sendToUser(BufferedWriter bw, String user, String message)
    {
        sendToChat(bw, "/w " + user + message);
        System.out.println(IDENT + " ~" + user + "~ " + message);
    }

}

I also have a User class that is not currently implemented. I have a plan for it, but its not 100% finished.

package com.k35trel.twitchbot;

import java.util.HashMap;

public class User
{
    public final String NAME;
    public String RANK;
    public int WARNINGS;
    private static HashMap<String, User> users = new HashMap<String, User>();

    public User(String NAME)
    {
        this.NAME = NAME;
        RANK = "VIEWER";
        WARNINGS = 0;
        users.put(NAME, this);
    }

    public static User getUser(String user)
    {
        if(users.containsKey(user))
        {
            return users.get(user);
        }
        else
        {
            return new User(user);
        }
    }

    public static void promoteViewer(String user)
    {
        User u = getUser(user);
        if(u.RANK.equals("VIEWER"))
        {
            u.RANK = "MODERATOR";
        }
    }

    public static void demoteModerator(String user)
    {
        User u = getUser(user);
        if(u.RANK.equals("MODERATOR"))
        {
            u.RANK = "VIEWER";
        }
    }

    public static void addWarning(String user)
    {
        User u = getUser(user);
        if(u.WARNINGS < 3)
        {
            u.WARNINGS++;
        }
    }

    public static void removeWarning(String user)
    {
        User u = getUser(user);
        if(u.WARNINGS > 0)
        {
            u.WARNINGS--;
        }
    }
}
\$\endgroup\$
3
\$\begingroup\$

In Main, PASS and IDENT should better be PASSWORD and USERNAME. The same in Bot, however there they (as well as HOST, CHANNEL and PORT) should be written in camelCase, because they are not constants there.


private static final String PASS = "oauth:xxxxx"; is a possible security hole. Instead of making it constant and thus readable in your source code as well as your memory, have the user type the password in. Then use a char[] instead of a string, and everywhere in your code where you need to use the password, overwrite the contents of the char[] with NULL characters afterwards; you can't overwrite a string, and just setting the reference to null still requires to wait for the garbage collector to actually delete it.


public void start()
{
    if(isRunning)
    {
        return;
    }

    init();
    loop();
}

Just returning from the start() method if the Bot is already running hides potential bugs. Since the bot is not started by user input, but by Main creating and starting it, you might throw an exception, because calling start() twice is a mistake of the code that is worth finding. In normal circumstances, it shouldn't be called twice anyway, and if you want to be sure you can expose the bot's running state with a method isRunning() so the caller can check first whether to call start() or not.

The name loop() tells you that you just made that method to make your start() method shorter, but not the intent of the method. To see what it does, you need to read the code, and to do that you have to jump through your sorce code. A better name would be for example waitForCommands(), if that is what it is doing.


In Bot, you have only one public method, and that is located at the end of the class. Better move the public members to the top, so that you can easily see what the class's interface is and what it is supposed to do. Then you can scroll down if you need details about the implementation.


private void executeCommand(int command)
{
    if(command == -1)
    {
        System.out.println("shutting down");
        isRunning = false;
    }
}

executeCommand does not really execute commands. The only command it knows is to shut down the bot. The actual command execution is done in chat.processCommands. Since process commands only returns -1 or 0 (like error codes, but not quite) you might return a boolean that simply determines whether to close the bot or not.


if(isRunning == false) { ... } should be if (!isRunning) { ... }.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.