Skip to content

Commit

Permalink
Update bot example files and command rework
Browse files Browse the repository at this point in the history
  • Loading branch information
quanticc committed Nov 30, 2020
1 parent 8592bc6 commit aaec973
Show file tree
Hide file tree
Showing 14 changed files with 731 additions and 512 deletions.
13 changes: 6 additions & 7 deletions core/src/test/java/discord4j/core/ExampleClientResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@

package discord4j.core;

import discord4j.core.support.BotSupport;
import discord4j.core.support.ExtraBotSupport;
import discord4j.core.command.CommandListener;
import discord4j.core.support.Commands;
import discord4j.rest.request.RouteMatcher;
import discord4j.rest.response.ResponseFunction;
import discord4j.rest.route.Routes;
import reactor.core.publisher.Mono;

/**
* An example bot showcasing how to implement global handlers against some API responses. See
Expand All @@ -37,9 +36,9 @@ public static void main(String[] args) {
// bad requests (400) while adding reactions will be suppressed
.onClientResponse(ResponseFunction.emptyOnErrorStatus(RouteMatcher.route(Routes.REACTION_CREATE), 400))
.build()
.withGateway(client -> Mono.when(
BotSupport.create(client).eventHandlers(),
ExtraBotSupport.create(client).eventHandlers()
));
.withGateway(client -> client.on(CommandListener.createWithPrefix("!!")
.on("echo", Commands::echo)
.on("exit", (req, res) -> res.getClient().logout())
.on("status", Commands::status)));
}
}
14 changes: 11 additions & 3 deletions core/src/test/java/discord4j/core/ExampleLogin.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@

package discord4j.core;

import discord4j.core.support.BotSupport;
import discord4j.core.command.CommandListener;
import discord4j.core.support.Commands;

public class ExampleLogin {

public static void main(String[] args) {
GatewayDiscordClient client = DiscordClient.create(System.getenv("token")).login().block();
BotSupport.create(client).eventHandlers().block();
GatewayDiscordClient client = DiscordClient.create(System.getenv("token"))
.login()
.block();

client.on(CommandListener.createWithPrefix("!!")
.on("echo", Commands::echo)
.on("exit", (req, res) -> res.getClient().logout())
.on("status", Commands::status))
.blockLast();
}
}
38 changes: 31 additions & 7 deletions core/src/test/java/discord4j/core/ExampleVoice.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,43 @@

package discord4j.core;

import discord4j.core.support.BotSupport;
import discord4j.core.support.ExtraBotSupport;
import discord4j.common.util.Snowflake;
import discord4j.core.command.CommandListener;
import discord4j.core.support.Commands;
import discord4j.core.support.VoiceSupport;
import discord4j.core.support.AddRandomReaction;
import discord4j.discordjson.json.ApplicationInfoData;
import reactor.core.publisher.Mono;

import static discord4j.core.support.Commands.isAuthor;

public class ExampleVoice {

public static void main(String[] args) {
GatewayDiscordClient client = DiscordClient.create(System.getenv("token")).login().block();
Mono.when(
BotSupport.create(client).eventHandlers(),
ExtraBotSupport.create(client).eventHandlers(),
VoiceSupport.create(client).eventHandlers())
GatewayDiscordClient client = DiscordClient.create(System.getenv("token"))
.login()
.block();

Mono<Long> ownerId = client.rest().getApplicationInfo()
.map(ApplicationInfoData::owner)
.map(user -> Snowflake.asLong(user.id()))
.cache();

CommandListener listener = CommandListener.createWithPrefix("!!")
.filter(req -> isAuthor(ownerId, req))
.on("echo", Commands::echo)
.on("exit", (req, res) -> res.getClient().logout())
.on("status", Commands::status)
.on("requestMembers", Commands::requestMembers)
.on("getMembers", Commands::getMembers)
.on("addRole", Commands::addRole)
.on("changeAvatar", Commands::changeAvatar)
.on("changeLogLevel", Commands::logLevelChange)
.on("react", new AddRandomReaction())
.on("userinfo", Commands::userInfo)
.on("reactionRemove", Commands::reactionRemove)
.on("leaveGuild", Commands::leaveGuild);

Mono.when(client.on(listener), VoiceSupport.create(client).eventHandlers()).block();
}
}
25 changes: 25 additions & 0 deletions core/src/test/java/discord4j/core/command/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/

package discord4j.core.command;

import org.reactivestreams.Publisher;

import java.util.function.BiFunction;

public interface Command extends BiFunction<CommandRequest, CommandResponse, Publisher<Void>> {
}
48 changes: 48 additions & 0 deletions core/src/test/java/discord4j/core/command/CommandHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/

package discord4j.core.command;

import org.reactivestreams.Publisher;

import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Predicate;

class CommandHandler implements BiFunction<CommandRequest, CommandResponse, Publisher<Void>>,
Predicate<CommandRequest> {

final Predicate<? super CommandRequest> condition;
final BiFunction<? super CommandRequest, ? super CommandResponse, ? extends Publisher<Void>> handler;

public CommandHandler(
Predicate<? super CommandRequest> condition,
BiFunction<? super CommandRequest, ? super CommandResponse, ? extends Publisher<Void>> handler) {
this.condition = Objects.requireNonNull(condition, "condition");
this.handler = Objects.requireNonNull(handler, "handler");
}

@Override
public Publisher<Void> apply(CommandRequest request, CommandResponse response) {
return handler.apply(request, response);
}

@Override
public boolean test(CommandRequest request) {
return condition.test(request);
}
}
110 changes: 110 additions & 0 deletions core/src/test/java/discord4j/core/command/CommandListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/

package discord4j.core.command;

import discord4j.common.annotations.Experimental;
import discord4j.core.event.ReactiveEventAdapter;
import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.object.entity.User;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;

import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Predicate;

@Experimental
public class CommandListener extends ReactiveEventAdapter {

private final Function<MessageCreateEvent, Publisher<String>> prefixFunction;
private final Function<? super CommandRequest, Mono<Boolean>> filter;
private final CopyOnWriteArrayList<CommandHandler> handlers = new CopyOnWriteArrayList<>();

CommandListener(Function<MessageCreateEvent, Publisher<String>> prefixFunction,
Function<? super CommandRequest, Mono<Boolean>> filter) {
this.prefixFunction = prefixFunction;
this.filter = filter;
}

CommandListener(CommandListener source) {
this.prefixFunction = source.prefixFunction;
this.filter = source.filter;
}

public static CommandListener create() {
return createWithPrefix(__ -> Mono.just(""));
}

public static CommandListener createWithPrefix(String prefix) {
return createWithPrefix(__ -> Mono.justOrEmpty(prefix));
}

public static CommandListener createWithPrefix(Function<MessageCreateEvent, Publisher<String>> prefixFunction) {
return new CommandListener(Objects.requireNonNull(prefixFunction, "prefixFunction"), __ -> Mono.just(true));
}

public CommandListener filter(Function<? super CommandRequest, Mono<Boolean>> condition) {
return new CommandListener(this.prefixFunction,
req -> this.filter.apply(req).flatMap(result -> condition.apply(req)));
}

public CommandListener on(String command, Command handler) {
return handle(request -> request.command().equals(command), handler);
}

public CommandListener handle(Predicate<? super CommandRequest> condition, Command handler) {
handlers.add(new CommandHandler(condition, handler));
return this;
}

public CommandListener handle(Command handler) {
handlers.add(new CommandHandler(__ -> true, handler));
return this;
}

@Override
public Publisher<?> onMessageCreate(MessageCreateEvent event) {
if (event.getMessage().getAuthor().map(User::isBot).orElse(true)) {
return Mono.empty();
} else {
String content = event.getMessage().getContent();
return Flux.defer(() -> prefixFunction.apply(event))
.filter(content::startsWith)
.map(prefix -> {
String trimmed = content.substring(prefix.length()).trim();
int endIndex = (trimmed.indexOf(' ') < 0) ? trimmed.length() : trimmed.indexOf(' ');
String commandName = trimmed.substring(0, endIndex);
String remaining = trimmed.substring(commandName.length()).trim();
return Tuples.of(
new DefaultCommandRequest(event, commandName, remaining),
new DefaultCommandResponse(event)
);
})
.filterWhen(exchange -> filter.apply(exchange.getT1()))
.flatMap(exchange -> handlers.stream()
.filter(handler -> handler.test(exchange.getT1()))
.map(handler -> handler.apply(exchange.getT1(), exchange.getT2()))
.findFirst()
.orElse(Mono.empty()));
// TODO: use CommandResponse to manage event lifecycle
}
}
}
56 changes: 56 additions & 0 deletions core/src/test/java/discord4j/core/command/CommandRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/

package discord4j.core.command;

import discord4j.core.GatewayDiscordClient;
import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.User;
import discord4j.core.object.entity.channel.GuildChannel;
import discord4j.rest.util.PermissionSet;
import reactor.core.publisher.Mono;

import java.util.Optional;

public interface CommandRequest {

MessageCreateEvent event();

String command();

String parameters();

default GatewayDiscordClient getClient() {
return event().getClient();
}

default Message getMessage() {
return event().getMessage();
}

default Optional<User> getAuthor() {
return event().getMessage().getAuthor();
}

default Mono<Boolean> hasPermission(PermissionSet requiredPermissions) {
return Mono.justOrEmpty(getAuthor().map(User::getId))
.flatMap(authorId -> getMessage().getChannel().ofType(GuildChannel.class)
.flatMap(channel -> channel.getEffectivePermissions(authorId))
.map(set -> set.containsAll(requiredPermissions)));
}
}
34 changes: 34 additions & 0 deletions core/src/test/java/discord4j/core/command/CommandResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/

package discord4j.core.command;

import discord4j.core.GatewayDiscordClient;
import discord4j.core.object.entity.channel.MessageChannel;
import discord4j.core.object.entity.channel.PrivateChannel;
import reactor.core.publisher.Mono;

public interface CommandResponse {

GatewayDiscordClient getClient();

Mono<MessageChannel> getReplyChannel();

Mono<PrivateChannel> getPrivateChannel();

// TODO: add error(...) methods for common error handling
}

0 comments on commit aaec973

Please sign in to comment.