initial commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.chat.bot.api;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Chat {
|
||||
boolean send(@NotNull String channel, @NotNull String name, @NotNull String message, boolean bottag, boolean publicId);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package eu.jonahbauer.chat.bot.api;
|
||||
|
||||
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||
import eu.jonahbauer.chat.bot.impl.ChatBotFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Accessors(makeFinal = true)
|
||||
public abstract class ChatBot {
|
||||
private static final ScopedValue<String> CHANNEL = ScopedValue.newInstance();
|
||||
private static final ScopedValue<Chat> CHAT = ScopedValue.newInstance();
|
||||
|
||||
protected final @NotNull BotConfig config;
|
||||
|
||||
protected ChatBot(@NotNull String name) {
|
||||
this(BotConfig.builder().name(name).build());
|
||||
}
|
||||
|
||||
protected ChatBot(@NotNull BotConfig defaultConfig) {
|
||||
var config = ChatBotFactory.BOT_CONFIG
|
||||
.orElseThrow(() -> new IllegalCallerException("ChatBot may only be instantiated via ChatBotFactory."));
|
||||
this.config = defaultConfig.merge(config);
|
||||
}
|
||||
|
||||
public final void onMessage(@NotNull Chat chat, @NotNull Message.Post message) {
|
||||
ScopedValue
|
||||
.where(CHANNEL, message.channel())
|
||||
.where(CHAT, chat)
|
||||
.run(() -> onMessage(message));
|
||||
}
|
||||
|
||||
@ApiStatus.OverrideOnly
|
||||
protected abstract void onMessage(@NotNull Message.Post message);
|
||||
|
||||
/**
|
||||
* Called during the normal shutdown process for a bot.
|
||||
*/
|
||||
@ApiStatus.OverrideOnly
|
||||
public void onStop() {}
|
||||
|
||||
protected void post(@NotNull String message) {
|
||||
post(message, null, null, null);
|
||||
}
|
||||
|
||||
protected void post(@NotNull String message, @Nullable String name, @Nullable Boolean bottag, @Nullable Boolean publicId) {
|
||||
Objects.requireNonNull(message, "message");
|
||||
name = Objects.requireNonNullElse(name, this.config.getName());
|
||||
bottag = Objects.requireNonNullElse(bottag, this.config.isBotTag());
|
||||
publicId = Objects.requireNonNullElse(publicId, this.config.isPublicId());
|
||||
|
||||
var chat = CHAT.orElseThrow(() -> new IllegalStateException("post() may only be called from inside onMessage(Post)"));
|
||||
var channel = CHANNEL.orElseThrow(() -> new IllegalStateException("post() may only be called from inside onMessage(Post)"));
|
||||
chat.send(channel, name, message, bottag, publicId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package eu.jonahbauer.chat.bot.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
property = "type"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = Message.Ping.class, name = "ping"),
|
||||
@JsonSubTypes.Type(value = Message.Pong.class, name = "pong"),
|
||||
@JsonSubTypes.Type(value = Message.Ack.class, name = "ack"),
|
||||
@JsonSubTypes.Type(value = Message.Post.class, name = "post")
|
||||
})
|
||||
public sealed interface Message {
|
||||
Ping PING = new Ping();
|
||||
|
||||
record Ping() implements Message { }
|
||||
|
||||
record Pong() implements Message { }
|
||||
|
||||
record Ack() implements Message { }
|
||||
|
||||
record Post(
|
||||
long id,
|
||||
@NotNull String name,
|
||||
@NotNull String message,
|
||||
@NotNull String channel,
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@NotNull LocalDateTime date,
|
||||
@Nullable Long delay,
|
||||
@JsonProperty("user_id")
|
||||
@Nullable Long userId,
|
||||
@JsonProperty("username")
|
||||
@Nullable String userName,
|
||||
@NotNull String color,
|
||||
int bottag
|
||||
) implements Message {
|
||||
public boolean bot() {
|
||||
return bottag != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.chat.bot.impl;
|
||||
|
||||
import lombok.experimental.StandardException;
|
||||
|
||||
@StandardException
|
||||
public class BotCreationException extends RuntimeException {
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package eu.jonahbauer.chat.bot.impl;
|
||||
|
||||
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ChatBotFactory<T extends ChatBot> {
|
||||
public static final ScopedValue<BotConfig> BOT_CONFIG = ScopedValue.newInstance();
|
||||
private static final ServiceLoader<ChatBot> SERVICE_LOADER = ServiceLoader.load(ChatBot.class);
|
||||
|
||||
private final @NotNull Class<T> type;
|
||||
private final @NotNull Supplier<T> delegate;
|
||||
|
||||
public static @NotNull Set<@NotNull Class<? extends ChatBot>> implementations() {
|
||||
return SERVICE_LOADER.stream().map(ServiceLoader.Provider::type).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ChatBotFactory(@NotNull Class<T> type) {
|
||||
if (!ChatBot.class.isAssignableFrom(type)) throw new IllegalArgumentException();
|
||||
|
||||
this.type = type;
|
||||
this.delegate = (Supplier<T>) SERVICE_LOADER.stream()
|
||||
.filter(p -> p.type().equals(type)).findFirst()
|
||||
.orElseThrow(() -> new BotCreationException("No suitable provider found: " + type.getName()));
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
ChatBotFactory(@NotNull Class<T> type, @NotNull Supplier<T> supplier) {
|
||||
if (!ChatBot.class.isAssignableFrom(type)) throw new IllegalArgumentException();
|
||||
this.type = type;
|
||||
this.delegate = Objects.requireNonNull(supplier, "supplier");
|
||||
}
|
||||
|
||||
public @NotNull Class<T> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public @NotNull T create() {
|
||||
return create(BotConfig.EMPTY);
|
||||
}
|
||||
|
||||
public @NotNull T create(@NotNull BotConfig config) {
|
||||
try {
|
||||
return ScopedValue.where(BOT_CONFIG, config).get(delegate);
|
||||
} catch (Throwable t) {
|
||||
throw new BotCreationException("Exception in constructor: " + type.getName(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
bot-api/src/main/java/module-info.java
Normal file
14
bot-api/src/main/java/module-info.java
Normal file
@@ -0,0 +1,14 @@
|
||||
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||
|
||||
module eu.jonahbauer.chat.bot.api {
|
||||
exports eu.jonahbauer.chat.bot.api;
|
||||
exports eu.jonahbauer.chat.bot.impl to eu.jonahbauer.chat.server;
|
||||
|
||||
requires transitive eu.jonahbauer.chat.bot.config;
|
||||
requires static transitive org.jetbrains.annotations;
|
||||
|
||||
requires static com.fasterxml.jackson.annotation;
|
||||
requires static lombok;
|
||||
|
||||
uses ChatBot;
|
||||
}
|
||||
Reference in New Issue
Block a user