initial commit

This commit is contained in:
jbb01
2023-09-12 19:47:07 +02:00
commit eadf1eaf5b
57 changed files with 3407 additions and 0 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,7 @@
package eu.jonahbauer.chat.bot.impl;
import lombok.experimental.StandardException;
@StandardException
public class BotCreationException extends RuntimeException {
}

View File

@@ -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);
}
}
}

View 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;
}