5
votes

I'm currently taking an intro to programming class in Java, and have recently started experimenting with the JDA tools to make a basic Discord bot for my server. Ideally, I want my bot to respond when someone says "Hello Apples!" by asking them for their name, then responding with "Hi !" if this message was sent by the same person who said "Hello Apples!" Right now my bot fails to await any user input past the initial "Hello Apples!" message, and spills out all of its text at once. I believe my current code is set up properly to ensure that the bot will only respond with "Hi !" if it receives a message from the same person who originally sent "Hi Apples!", but I can't be completely sure because it doesn't wait for an additional message, and as a result reads from the same message twice and prints out:
Hi! Tell me your name, or say "Stop"!
Hi Hi Apples!!
Wait your turn

I would really like to know how to create a "stop" of some sort, or a method that would cause the bot to wait for addition user input from the user who originally greeted the bot, and if possible, a way to set a time limit so the bot doesn't remain inoperable if they don't reply.

import net.dv8tion.jda.core.AccountType;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.JDABuilder;

public class Main {
  public static void main(String[] args) throws Exception {
    try {
      JDA api = new     JDABuilder(AccountType.BOT).setToken("NTQxMTMxMDc4MTY1ODU2MjYw.DzbGoQ.oFIM_py    pLMOc60qU1PgeeUXU8Qo").build();
      api.addEventListener(new MyEventListener());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

import net.dv8tion.jda.core.entities.Member;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.entities.MessageChannel;
import net.dv8tion.jda.core.entities.Role;
import net.dv8tion.jda.core.entities.User;
import net.dv8tion.jda.core.entities.*;
import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
import net.dv8tion.jda.core.hooks.ListenerAdapter;

public class MyEventListener extends ListenerAdapter {
  public void onMessageReceived(MessageReceivedEvent event) {
    if (event.getAuthor().isBot()) return;

    Message message = event.getMessage();
    String content = message.getContentRaw();
    MessageChannel channel = event.getChannel();

    if (content.startsWith("Hi Apples!")) {
      Member member = message.getMember();
      channel.sendMessage("Hi! Tell me your name, or say \"Stop\"!").queue();
      int n = 0;    
      while (n == 0) {
        Message message2 = event.getMessage(); 
        String content2 = message.getContentRaw();
        Member member2 = message2.getMember();
        String nick = member2.getNickname();
        if (member == member2) {
          channel.sendMessage("Hi " + content2 + "!").queue();
          n = 1;
        }
        else {
        }
          channel.sendMessage("Wait your turn " + nick + "!").queue();
        if (content2 == "Stop") {
          channel.sendMessage("Understood!").queue();
          n = 1;
        }
      }   
    }        
  }
}

My expected results are:
USER: Hi Apples!
BOT: Hi! Tell me your name, or say stop!
USER2: Hi!
BOT: Wait your turn USER2!
USER: Jimmy
BOT: Hi Jimmy!

Actual results: (Sent all at once)
Hi! Tell me your name, or say "Stop"!
Hi Hi Apples!!
Wait your turn (my discord nickname)!

1

1 Answers

3
votes

Since you are using an event-based framework you could implement this behavior with the use of a state-machine. Whenever you get your initial trigger, in this case "Hi Apple!" you would initiate a new state-machine for that text channel.

In this state-machine you handle message events until your termination signal arrives, in this case "Stop!".

The state-machine would be implemented using a switch-case in the event method together with a private state field. In this case you only have one interaction in the entire conversation so there is only one state which makes this pointless.

But for example in the case of having a conversation which is what I assume this will be later on you would need to go with the state-machine concept.

public class AppleStateMachine extends ListenerAdapter {
    private final long channelId, authorId; // id because keeping the entity would risk cache to become outdated

    public AppleStateMachine(MessageChannel channel, User author) {
        this.channelId = channel.getIdLong();
        this.authorId = author.getIdLong();
    }

    @Override
    public void onMessageReceived(MessageReceivedEvent event) {
        if (event.getAuthor().isBot()) return; // don't respond to other bots
        if (event.getChannel().getIdLong() != channelId) return; // ignore other channels
        MessageChannel channel = event.getChannel();
        String content = event.getMessage().getContentRaw();
        // since only one state is present you don't need a switch but that would be the concept if you had more than 1 interaction point in this protocol
        if (content.equals("Stop")) {
            channel.sendMessage("Understood!").queue();
            event.getJDA().removeEventListener(this); // stop listening
        }
        else if (event.getAuthor().getIdLong() == authorId) {
            channel.sendMessage("Hi " + content + "!").queue();
            event.getJDA().removeEventListener(this); // stop listening
        }
        else {
            channel.sendMessage("Wait your turn " + event.getMember().getEffectiveName() + "!").queue();
        }
    }
}

Then you only need to register an instance of this in your initial event handler

if (content.startsWith("Hi Apples!")) {
    channel.sendMessage("Hi! Tell me your name, or say \"Stop\"!").queue();
    event.getJDA().addEventListener(new AppleStateMachine(channel, member.getUser());
}

Make sure that you don't mix GuildMessageReceivedEvent and MessageReceivedEvent here, since they are fired in sequence and you might receive your initial message twice. Both your state machine and the message listener should listen to the same kind of event.

Another alternative to this is using the JDA-Utilities class EventWaiter.