Skip to content

Commit

Permalink
Add basic service manager to track status of individual parts of the bot
Browse files Browse the repository at this point in the history
  • Loading branch information
tterrag1098 committed Nov 27, 2020
1 parent 2133015 commit 1ad2f47
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 47 deletions.
110 changes: 63 additions & 47 deletions src/main/java/com/tterrag/k9/K9.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
import com.google.common.io.Files;
import com.tterrag.k9.commands.api.CommandRegistrar;
import com.tterrag.k9.listeners.CommandListener;
import com.tterrag.k9.listeners.EnderIOListener;
import com.tterrag.k9.listeners.IncrementListener;
import com.tterrag.k9.logging.PrettifyMessageCreate;
import com.tterrag.k9.mappings.Yarn2McpService;
import com.tterrag.k9.mappings.mcp.McpDownloader;
import com.tterrag.k9.mappings.yarn.YarnDownloader;
import com.tterrag.k9.util.ConvertAdmins;
import com.tterrag.k9.util.PaginatedMessageFactory;
import com.tterrag.k9.util.ServiceManager;
import com.tterrag.k9.util.Threads;

import discord4j.common.util.Snowflake;
Expand Down Expand Up @@ -109,6 +109,8 @@ public static void main(String[] argv) {
private final DiscordClient client;
@Getter
private final CommandRegistrar commands;
@Getter
private final ServiceManager services;

private static long initialConnectionTime;

Expand All @@ -119,6 +121,7 @@ public K9(Arguments args) {
PrettifyMessageCreate.client = client;

this.commands = new CommandRegistrar(this);
this.services = new ServiceManager();
}

public Mono<Void> start() {
Expand All @@ -127,57 +130,70 @@ public Mono<Void> start() {
Intent.GUILDS, Intent.GUILD_MEMBERS, Intent.GUILD_PRESENCES,
Intent.GUILD_MESSAGES, Intent.GUILD_MESSAGE_REACTIONS,
Intent.DIRECT_MESSAGES, Intent.DIRECT_MESSAGE_REACTIONS));
Function<EventDispatcher, Mono<Void>> eventDispatcher =
/*.withEventDispatcher(*/events -> {
Mono<Void> onReady = events.on(ReadyEvent.class)
.doOnNext(e -> {
log.info("Bot connected, starting up...");
log.info("Connected to {} guilds.", e.getGuilds().size());
})
.map(e -> e.getClient())
.flatMap(c -> Mono.zip( // These actions could be slow, so run them in parallel
c.getGuilds() // Print all connected guilds
.collectList()
.doOnNext(guilds -> guilds.forEach(g -> log.info("\t" + g.getName()))),
c.getSelf() // Set initial presence
.map(u ->"@" + u.getUsername() + " help")
.flatMap(s -> c.updatePresence(Presence.online(Activity.playing(s))))
))
.then();

Mono<Void> onInitialReady = events.on(ReadyEvent.class)
.next()
.doOnNext($ -> initialConnectionTime = System.currentTimeMillis())
.flatMap(e -> commands.complete(e.getClient()))
.then(YarnDownloader.INSTANCE.start())
.then(McpDownloader.INSTANCE.start())
.then(args.yarn2mcpOutput != null ? new Yarn2McpService(args.yarn2mcpOutput, args.yarn2mcpUser, args.yarn2mcpPass).start() : Mono.never());

Mono<Void> reactionHandler = events.on(ReactionAddEvent.class)
.flatMap(evt -> PaginatedMessageFactory.INSTANCE.onReactAdd(evt)
.doOnError(t -> log.error("Error paging message", t))
.onErrorResume($ -> Mono.empty())
.thenReturn(evt))
.then();

final CommandListener commandListener = new CommandListener(commands);

Mono<Void> messageHandler = events.on(MessageCreateEvent.class)
.filter(e -> e.getMessage().getAuthor().map(u -> !u.isBot()).orElse(true))
.flatMap(commandListener::onMessage)
.flatMap(IncrementListener.INSTANCE::onMessage)
.doOnNext(EnderIOListener.INSTANCE::onMessage)
.then();

return Mono.zip(onReady, onInitialReady, reactionHandler, messageHandler).then();
};//);

Function<EventDispatcher, Mono<Void>> onInitialReady = events -> events.on(ReadyEvent.class)
.next()
.doOnNext($ -> initialConnectionTime = System.currentTimeMillis())
.flatMap(e -> commands.complete(e.getClient()));

final CommandListener commandListener = new CommandListener(commands);

services
.eventService("Setup", ReadyEvent.class, events -> events
.doOnNext(e -> {
log.info("Bot connected, starting up...");
log.info("Connected to {} guilds.", e.getGuilds().size());
})
.map(e -> e.getClient())
.flatMap(c -> Mono.zip( // These actions could be slow, so run them in parallel
c.getGuilds() // Print all connected guilds
.collectList()
.doOnNext(guilds -> guilds.forEach(g -> log.info("\t" + g.getName()))),
c.getSelf() // Set initial presence
.map(u ->"@" + u.getUsername() + " help")
.flatMap(s -> c.updatePresence(Presence.online(Activity.playing(s))))
))
.then())

.eventService("Pagination", ReactionAddEvent.class, events -> events
.flatMap(evt -> PaginatedMessageFactory.INSTANCE.onReactAdd(evt)
.doOnError(t -> log.error("Error paging message", t))
.onErrorResume($ -> Mono.empty())
.thenReturn(evt))
.then())

.eventService("Commands", MessageCreateEvent.class, events -> events
.filter(this::isUser)
.flatMap(commandListener::onMessage))

.eventService("Increments", MessageCreateEvent.class, events -> events
.filter(this::isUser)
.flatMap(IncrementListener.INSTANCE::onMessage))

// I'll add this back when/if it's needed
/*
.eventService("EnderIO", MessageCreateEvent.class, events -> events
.filter(this::isUser)
.doOnNext(EnderIOListener.INSTANCE::onMessage))
*/
.service("Yarn Downloader", YarnDownloader.INSTANCE::start)
.service("MCP Downloader", McpDownloader.INSTANCE::start);

if (args.yarn2mcpOutput != null) {
final Yarn2McpService yarn2mcp = new Yarn2McpService(args.yarn2mcpOutput, args.yarn2mcpUser, args.yarn2mcpPass);
services.service("Yarn-Over-MCP", yarn2mcp::start);
}

return Mono.fromRunnable(commands::slurpCommands)
.then(gateway.login())
.flatMap(c -> eventDispatcher.apply(c.getEventDispatcher()).thenReturn(c))
.flatMap(c -> Mono.when(onInitialReady.apply(c.getEventDispatcher()), services.start(c)).thenReturn(c))
.flatMap(this::teardown);
}


private boolean isUser(MessageCreateEvent evt) {
return evt.getMessage().getAuthor().map(u -> !u.isBot()).orElse(true);
}

private Mono<Void> teardown(GatewayDiscordClient gatewayClient) {

// Handle "stop" and any future commands
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/tterrag/k9/commands/CommandAbout.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.tterrag.k9.listeners.CommandListener;
import com.tterrag.k9.util.Monos;

import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import reactor.core.publisher.Mono;

@Command
Expand All @@ -37,12 +38,18 @@ public void onRegister(K9 k9) {
@Override
public Mono<?> process(CommandContext ctx) {
String ver = K9.getVersion();
Object2BooleanMap<String> status = ctx.getK9().getServices().status();
StringBuilder statusText = new StringBuilder();
for (Object2BooleanMap.Entry<String> service : status.object2BooleanEntrySet()) {
statusText.append(service.getKey()).append(": ").append(service.getBooleanValue() ? "\u2705" : "\u274C").append("\n");
}
return ctx.getClient().getSelf()
.transform(Monos.flatZipWith(recentChanges, (u, changes) -> ctx.reply(spec ->
spec.setThumbnail(u.getAvatarUrl())
.setDescription("A bot for looking up Minecraft mappings, and other useful things.\nFor more info, try `" + CommandListener.getPrefix(ctx.getGuildId()) + "help`.")
.setTitle("K9 " + ver)
.setUrl("http://tterrag.com/k9")
.addField("Status", statusText.toString(), false)
.addField("Uptime", DurationFormatUtils.formatDurationWords((System.currentTimeMillis() - K9.getConnectionTimestamp()), true, false), false)
.addField("Source", "https://github.com/tterrag1098/K9", false)
.addField("Recent Changes", changes, false)
Expand Down
96 changes: 96 additions & 0 deletions src/main/java/com/tterrag/k9/util/ServiceManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.tterrag.k9.util;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

import org.reactivestreams.Publisher;

import discord4j.core.GatewayDiscordClient;
import discord4j.core.event.domain.Event;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import lombok.Getter;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
public class ServiceManager {

private interface ServiceInitializer<R, P extends Publisher<R>> {

String getName();

P start(GatewayDiscordClient client);
}

@Value
private static class BasicInitializer<R, P extends Publisher<R>> implements ServiceInitializer<R, P> {

@Getter(onMethod = @__({ @Override }))
String name;
Supplier<P> factory;

@Override
public P start(GatewayDiscordClient client) {
return getFactory().get();
}
}

@Value
private static class EventInitializer<T extends Event, R, P extends Publisher<R>> implements ServiceInitializer<R, P> {

@Getter(onMethod = @__({@Override}))
String name;
Class<T> eventClass;
Function<Flux<T>, P> factory;

@Override
public P start(GatewayDiscordClient client) {
return getFactory().apply(client.on(getEventClass()));
}
}

private final List<ServiceInitializer<?, ?>> initializers = new ArrayList<>();

private final Set<String> failedServices = new HashSet<>();

public <T extends Event, R, P extends Publisher<R>> ServiceManager eventService(String name, Class<T> eventClass, Function<Flux<T>, P> factory) {
this.initializers.add(new EventInitializer<>(name, eventClass, factory));
return this;
}

public <R, P extends Publisher<R>> ServiceManager service(String name, Supplier<P> factory) {
this.initializers.add(new BasicInitializer<>(name, factory));
return this;
}

public Mono<GatewayDiscordClient> start(GatewayDiscordClient client) {
return Mono.just(client)
.flatMap(c -> Mono.when(initializers.stream()
.map(i -> startService(i, c))
.toArray(Publisher[]::new)))
.thenReturn(client);
}

private <T extends Event, R, P extends Publisher<R>> Publisher<R> startService(ServiceInitializer<R, P> initializer, GatewayDiscordClient client) {
return Flux.from(initializer.start(client)).onErrorResume(t -> {
log.error("Service failed! Service name: " + initializer.getName(), t);
failedServices.add(initializer.getName());
return Mono.empty();
});
}

public Object2BooleanMap<String> status() {
Object2BooleanMap<String> ret = new Object2BooleanArrayMap<>(initializers.size());
for (ServiceInitializer<?, ?> initializer : initializers) {
ret.put(initializer.getName(), !failedServices.contains(initializer.getName()));
}
return ret;
}
}

0 comments on commit 1ad2f47

Please sign in to comment.