Skip to content

Commit

Permalink
Added docs again after the branch was reset
Browse files Browse the repository at this point in the history
  • Loading branch information
d-g-n committed Apr 13, 2017
1 parent 04cd777 commit 794b15c
Show file tree
Hide file tree
Showing 9 changed files with 571 additions and 0 deletions.
116 changes: 116 additions & 0 deletions docs/Basic-bot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
## Basic bot

This section covers the concepts surrounding the "core" of your bot, namely the `IDiscordClient` interface which represents your way to interact with your bot.

Most `Event` objects (such as the `MessageReceivedEvent`, for example) will expose a way to access the `IDiscordClient` instance it was dispatched from, it's highly advised to access it this way rather than using a singleton. The client can be accessed by calling `Event#getClient()`.

The code below demonstrates a basic bot implementation registering one listener for `MessageReceivedEvent` using both the `IListener` interface and the `EventSubscriber` notation. This is written assuming that the D4J version mentioned on the main page is included on the classpath via Maven, Gradle or however else you included the library. Refer to the README document on the main page of the GitHub repo for more details about that.

When `/test` is typed it'll send a message in that channel.

`MainRunner.java`:
```java
import sx.blah.discord.api.IDiscordClient;

public class MainRunner {

public static void main(String[] args){

if(args.length != 1){
System.out.println("Please enter the bots token as the first argument e.g java -jar thisjar.jar tokenhere");
return;
}

IDiscordClient cli = BotUtils.getBuiltDiscordClient(args[0]);

// Register a listener via the IListener interface
cli.getDispatcher().registerListener(new IListener<MessageReceivedEvent>() {
public void handle(MessageReceivedEvent event) {
if(event.getMessage().getContent().startsWith(BotUtils.BOT_PREFIX + "test"))
BotUtils.sendMesasge(event.getChannel(), "I am sending a message from an IListener listener");
}
});

// Register a listener via the EventSubscriber annotation which allows for organisation and delegation of events
cli.getDispatcher().registerListener(new MyEvents());

// Only login after all events are registered otherwise some may be missed.
cli.login();

}

}
```

BotUtils.java:
```java
import sx.blah.discord.api.ClientBuilder;
import sx.blah.discord.api.IDiscordClient;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.RateLimitException;
import sx.blah.discord.util.RequestBuffer;

class BotUtils {

// Constants for use throughout the bot
static String BOT_PREFIX = "/";

// Handles the creation and getting of a IDiscordClient object for a token
static IDiscordClient getBuiltDiscordClient(String token){

// The ClientBuilder object is where you will attach your params for configuring the instance of your bot.
// Such as withToken, setDaemon etc
return new ClientBuilder()
.withToken(token)
.build();

}

// Helper functions to make certain aspects of the bot easier to use.
static void sendMesasge(IChannel channel, String message){

// This might look weird but it'll be explained in another page.
RequestBuffer.request(() -> {
try{
channel.sendMessage(message);
} catch (DiscordException e){
System.err.println("Message could not be sent with error: ");
e.printStackTrace();
}
});

RequestBuffer.request(() -> {
try{
channel.sendMessage(message);
} catch (RateLimitException e){
System.out.println("Do some logging");
throw e;
}
});

}
}

```

MyEvents.java:
```java
import sx.blah.discord.api.events.EventSubscriber;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;

public class MyEvents {

@EventSubscriber
public void onMessageReceived(MessageReceivedEvent event){
if(event.getMessage().getContent().startsWith(BotUtils.BOT_PREFIX + "test"))
BotUtils.sendMesasge(event.getChannel(), "I am sending a message from an EventSubscriber listener");
}

}
```
Please note that in the traditional example the method that exposes the built `IDiscordClient` object contains a boolean option for logging in or building the client object, this is omitted due to the assumption that the correct flow is: Build the `IDiscordClient`, register events with the `EventDispatcher`, log in. This correct flow can also be achieved by passing in the listeners to register to the `ClientBuilder` object via `#registerListener/s` and then immediately calling `#login()` or `#build()`

Additionally, since exceptions are unchecked now, they do not need to be explicitly handled unless that's a desired behaviour.

This bot isn't designed to treat you to a three course dinner at a five star hotel or anything, it's purely to demonstrate the "correct" way of logging in and registering listeners.
174 changes: 174 additions & 0 deletions docs/Command-structures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
## Basic bot command structure

This page is somewhat of a follow on from the [[Basic bot]] page. Full code will can be seen below but will demonstrate a number of basic command systems that could be used. Note that this is a pretty diverse topic and many more implementations exist using other concepts such as annotations etc, this page is just a few examples to get you started. Listed from basic to most complex.

---

## Handling input

There are a variety of ways to effectively handle input, the simplest is to "tokenize" the message a user inputs, check it for a prefix, check the "command" associated with it and then pass the args, if any, to a function to handle that input. Note that the rest of the bot is the same as the code examples in [[Basic bot]]

### With string splitting and switching

CommandHandler.java (Drop in replacement for the basic one)
```java
import sx.blah.discord.api.events.EventSubscriber;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;

import java.util.*;

public class CommandHandler {

@EventSubscriber
public void onMessageReceived(MessageReceivedEvent event){

// Note for error handling, you'll probably want to log failed commands with a logger or sout
// In most cases it's not advised to annoy the user with a reply incase they didn't intend to trigger a
// command anyway, such as a user typing ?notacommand, the bot should not say "notacommand" doesn't exist in
// most situations. It's partially good practise and partially developer preference

// Given a message "/test arg1 arg2", argArray will contain ["/test", "arg1", "arg"]
String[] argArray = event.getMessage().getContent().split(" ");

// First ensure at least the command and prefix is present, the arg length can be handled by your command func
if(argArray.length == 0)
return;

// Check if the first arg (the command) starts with the prefix defined in the utils class
if(!argArray[0].startsWith(BotUtils.BOT_PREFIX))
return;

// Extract the "command" part of the first arg out by just ditching the first character
String commandStr = argArray[0].substring(1);

// Load the rest of the args in the array into a List for safer access
List<String> argsList = new ArrayList<>(Arrays.asList(argArray));
argsList.remove(0); // Remove the command

// Begin the switch to handle the string to command mappings. It's likely wise to pass the whole event or
// some part (IChannel) to the command handling it
switch (commandStr) {

case "test":
testCommand(event, argsList);
break;

}
}


private void testCommand(MessageReceivedEvent event, List<String> args){

BotUtils.sendMesasge(event.getChannel(), "You ran the test command with args: " + args);

}

}
```

The above code will produce the following output:
![Example of output](http://i.imgur.com/SIjDDEm.png)

More commands can be added by adding more switch cases

---

## Handling commands

This section will use string splitting to get the prefix, command and args as it's the simplest.

### Switch or if statement

This is the most basic command structure and whilst it's fast and easy to get going, it's not easily extendable or maintainable. So if you intend to use a system of many (>10) commands it's advised to see another route

See above for a switch example.


### Map of commands using an arbitrary command interface

Using a map allows for a far more modular approach, severely reducing the amount of manual boilerplate a developer has to write for various commands.

Command.java
```java
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;

import java.util.List;

public interface Command {

// Interface for a command to be implemented in the command map
void runCommand(MessageReceivedEvent event, List<String> args);

}
```

CommandHandler.java
```java
import sx.blah.discord.api.events.EventSubscriber;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;

import java.util.*;

public class CommandHandler {

// A static map of commands mapping from command string to the functional impl
private static Map<String, Command> commandMap = new HashMap<>();

// Statically populate the commandMap with the intended functionality
// Might be better practise to do this from an instantiated objects constructor
static {

commandMap.put("testcommand", (event, args) -> {
BotUtils.sendMesasge(event.getChannel(), "You ran the test command with args: " + args);
});

commandMap.put("ping", (event, args) -> {
BotUtils.sendMesasge(event.getChannel(), "pong");
});

}

@EventSubscriber
public void onMessageReceived(MessageReceivedEvent event){

// Note for error handling, you'll probably want to log failed commands with a logger or sout
// In most cases it's not advised to annoy the user with a reply incase they didn't intend to trigger a
// command anyway, such as a user typing ?notacommand, the bot should not say "notacommand" doesn't exist in
// most situations. It's partially good practise and partially developer preference

// Given a message "/test arg1 arg2", argArray will contain ["/test", "arg1", "arg"]
String[] argArray = event.getMessage().getContent().split(" ");

// First ensure at least the command and prefix is present, the arg length can be handled by your command func
if(argArray.length == 0)
return;

// Check if the first arg (the command) starts with the prefix defined in the utils class
if(!argArray[0].startsWith(BotUtils.BOT_PREFIX))
return;

// Extract the "command" part of the first arg out by just ditching the first character
String commandStr = argArray[0].substring(1);

// Load the rest of the args in the array into a List for safer access
List<String> argsList = new ArrayList<>(Arrays.asList(argArray));
argsList.remove(0); // Remove the command

// Instead of delegating the work to a switch, automatically do it via calling the mapping if it exists

if(commandMap.containsKey(commandStr))
commandMap.get(commandStr).runCommand(event, argsList);

}

}
```
(The above example takes advantage of lambdas from Java 8)

The above code will produce the following output:
![Example of output](http://i.imgur.com/vNIh4rj.png)

As usual, small disclaimer, this is not "the best code" this is purely intended to show some decent practises in handling commands without too much overhead, other solutions may be better for your use case.

---

9 changes: 9 additions & 0 deletions docs/Events-and-listeners.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Events in Discord terms represent "things that happen", registering and listening to these events is formally known as the Observer/Observable pattern and is used extensively throughout D4J. In brief, the Observer/Observable pattern operates off of the principle of "subscribing" to something happen. In an analogous example imagine subscribing to a YouTube channel, every time a new video is released by that creator, you're notified. The same thing happens here, you can "subscribe" to get updates about certain events via the `EventDispatcher`, when a new event of that type occurs, the method is called.

Most, if not all, events sent by Discord have an interface encoding it for reasonable usage in the D4J API, for example `MessageReceivedEvent` is an event that's dispatched when a message is received by the bot from any other user, this includes DMs. A full list of events that potentially have encoded interfaces can be found here https://discordapp.com/developers/docs/topics/gateway

Some specifics:
- GuildCreateEvent is dispatched when the data about that guild is sent to the bot, contrary to popular belief this event can multiple times, on startup and on initial joining of the guild.
- ReadyEvent is dispatched only after all shards/guilds are ready and all data is received.

The syntax for using both the `EventSubscriber` and `IListener` methods of registering events can be found on the main D4J README. A practical example of both can also be found under the [[Basic bot]] page.
49 changes: 49 additions & 0 deletions docs/Making-embedded-content-using-EmbedBuilder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Embedded content

### What are embeds?

Embeds are the pretty rich text content you see that looks a little something like this:
![Baristron](http://i.imgur.com/KvJQ6Ko.png)

The above example is from the bot "Baristron" developed by chrislo27

### How do I make them?

Below is a graphical example with the partner code used to represent it. In D4J all embeds are created through the `EmbedBuilder` class. The code should be fairly self explanatory. For full details about any of the methods used to configure the builder, please check the `EmbedBuilder` javadocs.

Embed:

![Embed example](http://i.imgur.com/0zCADHo.png)

Code:

```java
EmbedBuilder builder = new EmbedBuilder();

builder.appendField("fieldTitleInline", "fieldContentInline", true);
builder.appendField("fieldTitleInline2", "fieldContentInline2", true);
builder.appendField("fieldTitleNotInline", "fieldContentNotInline", false);
builder.appendField(":tada: fieldWithCoolThings :tada:", "[hiddenLink](http://i.imgur.com/Y9utuDe.png)", false);

builder.withAuthorName("authorName");
builder.withAuthorIcon("http://i.imgur.com/PB0Soqj.png");
builder.withAuthorUrl("http://i.imgur.com/oPvYFj3.png");

builder.withColor(255, 0, 0);
builder.withDesc("withDesc");
builder.withDescription("withDescription");
builder.withTitle("withTitle");
builder.withTimestamp(100);
builder.withUrl("http://i.imgur.com/IrEVKQq.png");
builder.withImage("http://i.imgur.com/agsp5Re.png");

builder.withFooterIcon("http://i.imgur.com/Ch0wy1e.png");
builder.withFooterText("footerText");
builder.withFooterIcon("http://i.imgur.com/TELh8OT.png");
builder.withThumbnail("http://i.imgur.com/7heQOCt.png");

builder.appendDesc(" + appendDesc");
builder.appendDescription(" + appendDescription");

RequestBuffer.request(() -> event.getChannel().sendMessage(builder.build()));
```
Loading

0 comments on commit 794b15c

Please sign in to comment.