the return type
12 | */
13 | @FunctionalInterface
14 | public interface CheckedFunction extends Function
{
15 |
16 | @Override
17 | default R apply(P p) {
18 | try {
19 | return doApply(p);
20 | } catch (RuntimeException e) {
21 | throw e;
22 | } catch (Exception e) {
23 | throw new CommandRuntimeException(e);
24 | }
25 | }
26 |
27 | R doApply(P p) throws Exception;
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/CheckedRunnable.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function;
2 |
3 | import net.robinfriedli.aiode.exceptions.CommandRuntimeException;
4 |
5 | /**
6 | * Runnable that handles checked exceptions by wrapping them into {@link CommandRuntimeException}
7 | */
8 | @FunctionalInterface
9 | public interface CheckedRunnable extends Runnable {
10 |
11 | @Override
12 | default void run() {
13 | try {
14 | doRun();
15 | } catch (RuntimeException e) {
16 | throw e;
17 | } catch (Exception e) {
18 | throw new CommandRuntimeException(e);
19 | }
20 | }
21 |
22 | void doRun() throws Exception;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/FunctionInvoker.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function;
2 |
3 | import java.util.function.Consumer;
4 | import java.util.function.Function;
5 |
6 | import net.robinfriedli.exec.Mode;
7 |
8 | /**
9 | * Implementations may manage calling a function in a certain way, e.g. setting up a transaction.
10 | *
11 | * @param
the type of the function parameter
12 | */
13 | public interface FunctionInvoker
{
14 |
15 | V invokeFunction(Function function);
16 |
17 | default void invokeConsumer(Consumer
consumer) {
18 | invokeFunction(p -> {
19 | consumer.accept(p);
20 | return null;
21 | });
22 | }
23 |
24 | V invokeFunction(Mode mode, Function function);
25 |
26 | default void invokeConsumer(Mode mode, Consumer
consumer) {
27 | invokeFunction(mode, p -> {
28 | consumer.accept(p);
29 | return null;
30 | });
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/LoggingRunnable.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function;
2 |
3 | import net.robinfriedli.aiode.Aiode;
4 |
5 | @FunctionalInterface
6 | public interface LoggingRunnable extends CheckedRunnable {
7 |
8 | @Override
9 | default void run() {
10 | try {
11 | doRun();
12 | } catch (Exception e) {
13 | Aiode.LOGGER.error("Uncaught exception in thread " + Thread.currentThread(), e);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/modes/RecursionPreventionMode.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function.modes;
2 |
3 | import java.util.Set;
4 | import java.util.concurrent.Callable;
5 |
6 | import com.google.common.collect.Sets;
7 | import net.robinfriedli.aiode.concurrent.ThreadContext;
8 | import net.robinfriedli.exec.AbstractNestedModeWrapper;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | /**
12 | * Mode that breaks recursion by simply not executing tasks with this mode applied if there already is a task with this
13 | * mode and the same recursion key running in the current thread.
14 | */
15 | public class RecursionPreventionMode extends AbstractNestedModeWrapper {
16 |
17 | private final String key;
18 |
19 | public RecursionPreventionMode(String key) {
20 | this.key = key;
21 | }
22 |
23 | @NotNull
24 | @Override
25 | public Callable wrap(@NotNull Callable callable) {
26 | return () -> {
27 | ThreadContext threadContext = ThreadContext.Current.get();
28 | Set usedKeys;
29 | if (threadContext.isInstalled("recursion_prevention_keys")) {
30 | //noinspection unchecked
31 | usedKeys = threadContext.require("recursion_prevention_keys", Set.class);
32 |
33 | if (!usedKeys.add(key)) {
34 | // task was already running in this mode, this is a recursive call -> return
35 | return null;
36 | }
37 | } else {
38 | usedKeys = Sets.newHashSet(key);
39 | threadContext.install("recursion_prevention_keys", usedKeys);
40 | }
41 |
42 | try {
43 | return callable.call();
44 | } finally {
45 | usedKeys.remove(key);
46 | }
47 | };
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/modes/SpotifyAuthorizationMode.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function.modes;
2 |
3 | import java.util.concurrent.Callable;
4 |
5 | import net.robinfriedli.exec.AbstractNestedModeWrapper;
6 | import net.robinfriedli.exec.Mode;
7 | import org.jetbrains.annotations.NotNull;
8 | import se.michaelthelin.spotify.SpotifyApi;
9 | import se.michaelthelin.spotify.model_objects.credentials.ClientCredentials;
10 |
11 | /**
12 | * Mode that runs the given task with default Spotify credentials applied
13 | */
14 | public class SpotifyAuthorizationMode extends AbstractNestedModeWrapper {
15 |
16 | private final SpotifyApi spotifyApi;
17 |
18 | public SpotifyAuthorizationMode(SpotifyApi spotifyApi) {
19 | this.spotifyApi = spotifyApi;
20 | }
21 |
22 | @Override
23 | public @NotNull Callable wrap(@NotNull Callable callable) {
24 | return () -> {
25 | String prevAccessToken = spotifyApi.getAccessToken();
26 | try {
27 | ClientCredentials credentials = spotifyApi.clientCredentials().build().execute();
28 | spotifyApi.setAccessToken(credentials.getAccessToken());
29 |
30 | return callable.call();
31 | } finally {
32 | spotifyApi.setAccessToken(prevAccessToken);
33 | }
34 | };
35 | }
36 |
37 | public Mode getMode(SpotifyApi spotifyApi) {
38 | return Mode.create().with(new SpotifyAuthorizationMode(spotifyApi));
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/modes/SpotifyMarketMode.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function.modes;
2 |
3 | import java.util.concurrent.Callable;
4 |
5 | import com.neovisionaries.i18n.CountryCode;
6 | import net.robinfriedli.aiode.audio.spotify.SpotifyContext;
7 | import net.robinfriedli.aiode.concurrent.ThreadContext;
8 | import net.robinfriedli.exec.AbstractNestedModeWrapper;
9 | import org.jetbrains.annotations.NotNull;
10 |
11 | /**
12 | * Set the Spotify market used for requests in the current {@link SpotifyContext}.
13 | */
14 | public class SpotifyMarketMode extends AbstractNestedModeWrapper {
15 |
16 | private final CountryCode market;
17 |
18 | public SpotifyMarketMode(CountryCode market) {
19 | this.market = market;
20 | }
21 |
22 | @Override
23 | public @NotNull Callable wrap(@NotNull Callable callable) {
24 | return () -> {
25 | SpotifyContext spotifyContext = ThreadContext.Current.get().getOrCompute(SpotifyContext.class, SpotifyContext::new);
26 | spotifyContext.setMarket(market);
27 | return callable.call();
28 | };
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/function/modes/SpotifyUserAuthorizationMode.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function.modes;
2 |
3 | import java.util.concurrent.Callable;
4 |
5 | import net.robinfriedli.aiode.login.Login;
6 | import net.robinfriedli.exec.AbstractNestedModeWrapper;
7 | import org.jetbrains.annotations.NotNull;
8 | import se.michaelthelin.spotify.SpotifyApi;
9 |
10 | /**
11 | * Mode that runs the given task with Spotify credentials for the given Login applied applied
12 | */
13 | public class SpotifyUserAuthorizationMode extends AbstractNestedModeWrapper {
14 |
15 | private final Login login;
16 | private final SpotifyApi spotifyApi;
17 |
18 | public SpotifyUserAuthorizationMode(Login login, SpotifyApi spotifyApi) {
19 | this.login = login;
20 | this.spotifyApi = spotifyApi;
21 | }
22 |
23 | @Override
24 | public @NotNull Callable wrap(@NotNull Callable callable) {
25 | return () -> {
26 | String prevAccessToken = spotifyApi.getAccessToken();
27 | try {
28 | spotifyApi.setAccessToken(login.getAccessToken());
29 | return callable.call();
30 | } finally {
31 | spotifyApi.setAccessToken(prevAccessToken);
32 | }
33 | };
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/customchange/PermissionTargetTypeInitialValues.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.customchange;
2 |
3 | import net.robinfriedli.aiode.command.PermissionTarget;
4 |
5 | public class PermissionTargetTypeInitialValues extends InsertEnumLookupValuesChange {
6 |
7 | @Override
8 | protected PermissionTarget.TargetType[] getValues() {
9 | return PermissionTarget.TargetType.values();
10 | }
11 |
12 | @Override
13 | protected String getTableName() {
14 | return "permission_type";
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/customchange/PlaybackHistorySourceInitialValues.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.customchange;
2 |
3 | import net.robinfriedli.aiode.audio.Playable;
4 |
5 | public class PlaybackHistorySourceInitialValues extends InsertEnumLookupValuesChange {
6 |
7 | @Override
8 | protected Playable.Source[] getValues() {
9 | return Playable.Source.values();
10 | }
11 |
12 | @Override
13 | protected String getTableName() {
14 | return "playback_history_source";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/customchange/ScriptUsageInitialValues.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.customchange;
2 |
3 | import net.robinfriedli.aiode.scripting.ScriptUsageType;
4 |
5 | public class ScriptUsageInitialValues extends InsertEnumLookupValuesChange {
6 |
7 | @Override
8 | protected ScriptUsageType[] getValues() {
9 | return ScriptUsageType.values();
10 | }
11 |
12 | @Override
13 | protected String getTableName() {
14 | return "script_usage";
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/customchange/SpotifyItemKindInitialValues.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.customchange;
2 |
3 | import net.robinfriedli.aiode.audio.spotify.SpotifyTrackKind;
4 |
5 | public class SpotifyItemKindInitialValues extends InsertEnumLookupValuesChange {
6 |
7 | @Override
8 | protected SpotifyTrackKind[] getValues() {
9 | return SpotifyTrackKind.values();
10 | }
11 |
12 | @Override
13 | protected String getTableName() {
14 | return "spotify_item_kind";
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/interceptors/PlaylistItemTimestampInterceptor.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.interceptors;
2 |
3 | import java.io.Serializable;
4 | import java.util.Date;
5 |
6 | import org.slf4j.Logger;
7 |
8 | import net.robinfriedli.aiode.entities.PlaylistItem;
9 | import org.hibernate.Interceptor;
10 | import org.hibernate.type.Type;
11 |
12 | public class PlaylistItemTimestampInterceptor extends ChainableInterceptor {
13 |
14 | public PlaylistItemTimestampInterceptor(Interceptor next, Logger logger) {
15 | super(next, logger);
16 | }
17 |
18 | @Override
19 | public void onSaveChained(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
20 | if (entity instanceof PlaylistItem) {
21 | Date createdTimestamp = new Date();
22 | ((PlaylistItem) entity).setCreatedTimestamp(createdTimestamp);
23 | for (int i = 0; i < propertyNames.length; i++) {
24 | if ("createdTimestamp".equals(propertyNames[i])) {
25 | state[i] = createdTimestamp;
26 | }
27 | }
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/qb/PredicateBuilder.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.qb;
2 |
3 | import javax.annotation.CheckReturnValue;
4 |
5 | import jakarta.persistence.criteria.CriteriaBuilder;
6 | import jakarta.persistence.criteria.From;
7 | import jakarta.persistence.criteria.Predicate;
8 |
9 | /**
10 | * Functional interface that builds a JPA predicate by applying the provided criteria builder and query root.
11 | */
12 | @FunctionalInterface
13 | public interface PredicateBuilder {
14 |
15 | Predicate build(CriteriaBuilder cb, From, ?> root, SubQueryBuilderFactory subQueryFactory);
16 |
17 | @CheckReturnValue
18 | default PredicateBuilder combine(PredicateBuilder other) {
19 | return (cb, root, subQueryFactory) -> cb.and(this.build(cb, root, subQueryFactory), other.build(cb, root, subQueryFactory));
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/qb/QueryConsumer.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.qb;
2 |
3 | import jakarta.persistence.criteria.AbstractQuery;
4 | import jakarta.persistence.criteria.CriteriaBuilder;
5 | import jakarta.persistence.criteria.CriteriaQuery;
6 | import jakarta.persistence.criteria.From;
7 | import jakarta.persistence.criteria.Subquery;
8 |
9 | /**
10 | * Custom consumer that accepts a Query root, CriteriaBuilder and parameterized JPA query implementation, either
11 | * {@link CriteriaQuery} or {@link Subquery}.
12 | *
13 | * @param the type of JPA query implementation
14 | */
15 | @FunctionalInterface
16 | public interface QueryConsumer> {
17 |
18 | void accept(From, ?> root, CriteriaBuilder cb, Q query);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/qb/SimplePredicateBuilder.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.qb;
2 |
3 | import jakarta.persistence.criteria.CriteriaBuilder;
4 | import jakarta.persistence.criteria.From;
5 | import jakarta.persistence.criteria.Predicate;
6 |
7 | /**
8 | * Simplification of {@link PredicateBuilder} with only the essential parameters.
9 | */
10 | @FunctionalInterface
11 | public interface SimplePredicateBuilder extends PredicateBuilder {
12 |
13 | Predicate build(CriteriaBuilder cb, From, ?> root);
14 |
15 | @Override
16 | default Predicate build(CriteriaBuilder cb, From, ?> root, SubQueryBuilderFactory subQueryFactory) {
17 | return build(cb, root);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/qb/interceptor/QueryInterceptor.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.qb.interceptor;
2 |
3 | import net.robinfriedli.aiode.persist.qb.QueryBuilder;
4 |
5 | /**
6 | * Interface for query interceptors that are called when building a {@link QueryBuilder} implementation. Implementations
7 | * may, for example, filter the query based on current guild context, only returning results owned by the current guild
8 | * without having to explicitly specify the condition in the query each time.
9 | */
10 | public interface QueryInterceptor {
11 |
12 | /**
13 | * Executes the actual intercept logic that modifies the given query builder instance which is currently being built.
14 | *
15 | * @param queryBuilder the query builder to extend
16 | */
17 | void intercept(QueryBuilder, ?, ?, ?> queryBuilder);
18 |
19 | /**
20 | * Executes a check whether this interceptor should get involved with the current query, based on the selected
21 | * entity model.
22 | *
23 | * @param entityClass the selected entity model
24 | * @return true
if this interceptor should modify queries selecting the provided entity model
25 | */
26 | boolean shouldIntercept(Class> entityClass);
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/persist/tasks/PersistTask.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.persist.tasks;
2 |
3 | public interface PersistTask {
4 |
5 | E perform() throws Exception;
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/rest/ExceptionHandlerAdvice.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.rest;
2 |
3 | import net.robinfriedli.aiode.rest.exceptions.MissingAccessException;
4 | import org.springframework.http.ResponseEntity;
5 | import org.springframework.web.bind.annotation.ControllerAdvice;
6 | import org.springframework.web.bind.annotation.ExceptionHandler;
7 |
8 | @ControllerAdvice
9 | public class ExceptionHandlerAdvice {
10 |
11 | @ExceptionHandler
12 | public ResponseEntity handleMissingAccessException(MissingAccessException e) {
13 | return ResponseEntity.status(403).body(e.getMessage());
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/rest/RequestContext.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.rest;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 |
5 | public class RequestContext {
6 |
7 | private final HttpServletRequest request;
8 |
9 | public RequestContext(HttpServletRequest request) {
10 | this.request = request;
11 | }
12 |
13 | public HttpServletRequest getRequest() {
14 | return request;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/rest/ServletCustomizer.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.rest;
2 |
3 | import org.springframework.boot.web.server.MimeMappings;
4 | import org.springframework.boot.web.server.WebServerFactoryCustomizer;
5 | import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | public class ServletCustomizer implements WebServerFactoryCustomizer {
10 |
11 | @Override
12 | public void customize(ConfigurableServletWebServerFactory factory) {
13 | MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
14 | mappings.add("wasm", "application/wasm");
15 | factory.setMimeMappings(mappings);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/rest/annotations/AuthenticationRequired.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.rest.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Target;
5 |
6 | /**
7 | * Annotations for handler methods that states that the client must be connected to an active session to use this endpoint.
8 | * If {@link #requiredPermissions()} is not empty this further checks whether the member connected with this session
9 | * has the required permissions.
10 | */
11 | @Target(ElementType.METHOD)
12 | public @interface AuthenticationRequired {
13 |
14 | /**
15 | * @return a string array containing the accessConfiguration permissionIdentifiers required to access this endpoint.
16 | */
17 | String[] requiredPermissions() default {};
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/rest/exceptions/MissingAccessException.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.rest.exceptions;
2 |
3 | /**
4 | * Thrown when the bot cannot connect to the guild or user specified by the session of the web client, i.e. when getGuild
5 | * or getMember return null.
6 | */
7 | public class MissingAccessException extends RuntimeException {
8 |
9 | public MissingAccessException() {
10 | super();
11 | }
12 |
13 | public MissingAccessException(String message) {
14 | super(message);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/scripting/GroovyVariableProvider.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.scripting;
2 |
3 | import java.util.Map;
4 |
5 | public interface GroovyVariableProvider {
6 |
7 | Map provideVariables();
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/scripting/ScriptUsageType.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.scripting;
2 |
3 | public enum ScriptUsageType {
4 | script,
5 | interceptor,
6 | finalizer,
7 | trigger,
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/scripting/variables/ExecutionContextVariableProvider.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.scripting.variables;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 |
6 | import net.robinfriedli.aiode.concurrent.ExecutionContext;
7 | import net.robinfriedli.aiode.scripting.GroovyVariableProvider;
8 |
9 | public class ExecutionContextVariableProvider implements GroovyVariableProvider {
10 |
11 | @Override
12 | public Map provideVariables() {
13 | return ExecutionContext.Current.optional().map(ExecutionContext::getScriptParameters).orElse(Collections.emptyMap());
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/scripting/variables/SingletonVariableProvider.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.scripting.variables;
2 |
3 | import java.util.Map;
4 |
5 | import net.robinfriedli.aiode.Aiode;
6 | import net.robinfriedli.aiode.scripting.GroovyVariableProvider;
7 |
8 | public class SingletonVariableProvider implements GroovyVariableProvider {
9 |
10 | @Override
11 | public Map provideVariables() {
12 | Aiode aiode = Aiode.get();
13 | return Map.of(
14 | "messages", aiode.getMessageService(),
15 | "securityManager", aiode.getSecurityManager(),
16 | "audioManager", aiode.getAudioManager()
17 | );
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/scripting/variables/ThreadContextVariableProvider.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.scripting.variables;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 |
6 | import net.robinfriedli.aiode.command.AbstractCommand;
7 | import net.robinfriedli.aiode.command.Command;
8 | import net.robinfriedli.aiode.concurrent.ThreadContext;
9 | import net.robinfriedli.aiode.scripting.GroovyVariableProvider;
10 |
11 | public class ThreadContextVariableProvider implements GroovyVariableProvider {
12 |
13 | @Override
14 | public Map provideVariables() {
15 | return ThreadContext.Current.optional(Command.class).map(command -> {
16 | if (command instanceof AbstractCommand) {
17 | return Map.of(
18 | "command", command,
19 | "input", ((AbstractCommand) command).getCommandInput()
20 | );
21 | } else {
22 | return Map.of("command", command);
23 | }
24 | }).orElse(Collections.emptyMap());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/servers/ResourceHandler.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.servers;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 |
8 | import org.slf4j.LoggerFactory;
9 |
10 | import com.sun.net.httpserver.HttpExchange;
11 | import com.sun.net.httpserver.HttpHandler;
12 |
13 | /**
14 | * Handler responsible for loading binaries in the resources-public directory
15 | */
16 | public class ResourceHandler implements HttpHandler {
17 |
18 | @Override
19 | public void handle(HttpExchange exchange) throws IOException {
20 | try {
21 | byte[] bytes = Files.readAllBytes(Path.of("." + exchange.getRequestURI().toString()));
22 | exchange.sendResponseHeaders(200, bytes.length);
23 | OutputStream responseBody = exchange.getResponseBody();
24 | responseBody.write(bytes);
25 | responseBody.close();
26 | } catch (Exception e) {
27 | String response = e.toString();
28 | exchange.sendResponseHeaders(500, response.getBytes().length);
29 | OutputStream responseBody = exchange.getResponseBody();
30 | responseBody.write(response.getBytes());
31 | responseBody.close();
32 | LoggerFactory.getLogger(getClass()).error("Error in HttpHandler", e);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/servers/ServerUtil.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.servers;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.nio.charset.StandardCharsets;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import org.apache.http.NameValuePair;
13 | import org.apache.http.client.utils.URLEncodedUtils;
14 |
15 | import com.sun.net.httpserver.HttpExchange;
16 |
17 | public class ServerUtil {
18 |
19 | public static void handleError(HttpExchange exchange, Throwable e) throws IOException {
20 | String html = Files.readString(Path.of("html/default_error_page.html"));
21 | String response = String.format(html, e.getMessage());
22 | exchange.sendResponseHeaders(500, response.getBytes().length);
23 | OutputStream responseBody = exchange.getResponseBody();
24 | responseBody.write(response.getBytes());
25 | responseBody.close();
26 | }
27 |
28 | public static Map getParameters(HttpExchange exchange) {
29 | List parameters = URLEncodedUtils.parse(exchange.getRequestURI(), StandardCharsets.UTF_8);
30 | Map parameterMap = new HashMap<>();
31 | parameters.forEach(param -> parameterMap.put(param.getName(), param.getValue()));
32 | return parameterMap;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/util/EmojiConstants.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.util;
2 |
3 | public class EmojiConstants {
4 |
5 | public static final String PLAY_PAUSE = "⏯";
6 | public static final String PLAY = "\u25B6";
7 | public static final String PAUSE = "\u23F8";
8 | public static final String REWIND = "⏮";
9 | public static final String SKIP = "⏭";
10 | public static final String SHUFFLE = "\uD83D\uDD00";
11 | public static final String REPEAT = "\uD83D\uDD01";
12 | public static final String REPEAT_ONE = "\uD83D\uDD02";
13 | public static final String VOLUME = "\uD83D\uDD08";
14 | public static final String VOLUME_UP = "\uD83D\uDD0A";
15 | public static final String VOLUME_DOWN = "\uD83D\uDD09";
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/net/robinfriedli/aiode/util/MutableTuple2.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.util;
2 |
3 | public class MutableTuple2 {
4 |
5 | private L left;
6 | private R right;
7 |
8 | public MutableTuple2(L left, R right) {
9 | this.left = left;
10 | this.right = right;
11 | }
12 |
13 | public static MutableTuple2 of(L left, R right) {
14 | return new MutableTuple2<>(left, right);
15 | }
16 |
17 | public L getLeft() {
18 | return left;
19 | }
20 |
21 | public void setLeft(L left) {
22 | this.left = left;
23 | }
24 |
25 | public R getRight() {
26 | return right;
27 | }
28 |
29 | public void setRight(R right) {
30 | this.right = right;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/AbstractPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables
2 |
3 | import com.google.common.collect.Lists
4 | import net.robinfriedli.aiode.audio.Playable
5 | import net.robinfriedli.aiode.audio.queue.AudioQueue
6 | import net.robinfriedli.aiode.audio.queue.PlayableContainerQueueFragment
7 | import net.robinfriedli.aiode.audio.queue.QueueFragment
8 |
9 | /**
10 | * Abstract class for anything that represents a [Playable] or contains [Playable] instances, e.g. Spotify tracks, playlists
11 | * albums etc.
12 | */
13 | abstract class AbstractPlayableContainer(private val item: T) : PlayableContainer {
14 |
15 | var playables: List? = null
16 |
17 | final override fun loadPlayables(playableFactory: PlayableFactory): List {
18 | if (playables == null) {
19 | playables = doLoadPlayables(playableFactory)
20 | }
21 |
22 | return playables!!
23 | }
24 |
25 | override fun createQueueFragment(playableFactory: PlayableFactory, queue: AudioQueue): QueueFragment? {
26 | val playables = loadPlayables(playableFactory)
27 | return if (playables.isNotEmpty()) {
28 | PlayableContainerQueueFragment(queue, Lists.newArrayList(playables), this)
29 | } else {
30 | null
31 | }
32 | }
33 |
34 | abstract fun doLoadPlayables(playableFactory: PlayableFactory): List
35 |
36 | override fun getItem(): T {
37 | return item
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/AbstractSinglePlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.queue.AudioQueue
5 | import net.robinfriedli.aiode.audio.queue.QueueFragment
6 | import net.robinfriedli.aiode.audio.queue.SinglePlayableQueueFragment
7 | import java.util.*
8 |
9 | abstract class AbstractSinglePlayableContainer(item: T) : AbstractPlayableContainer(item) {
10 |
11 | override fun doLoadPlayables(playableFactory: PlayableFactory): List {
12 | val playable = doLoadPlayable(playableFactory)
13 | return if (playable != null) {
14 | Collections.singletonList(playable)
15 | } else {
16 | Collections.emptyList()
17 | }
18 | }
19 |
20 | override fun loadPlayable(playableFactory: PlayableFactory): Playable? {
21 | val playables = loadPlayables(playableFactory)
22 | return when {
23 | playables.size == 1 -> playables[0]
24 | playables.isNotEmpty() -> throw UnsupportedOperationException("PlayableContainer $this does not resolve to a single Playable")
25 | else -> null
26 | }
27 | }
28 |
29 | override fun createQueueFragment(playableFactory: PlayableFactory, queue: AudioQueue): QueueFragment? {
30 | val playable = loadPlayable(playableFactory)
31 | return if (playable != null) {
32 | SinglePlayableQueueFragment(queue, playable, this)
33 | } else {
34 | null
35 | }
36 | }
37 |
38 | abstract fun doLoadPlayable(playableFactory: PlayableFactory): Playable?
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/PlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.queue.AudioQueue
5 | import net.robinfriedli.aiode.audio.queue.QueueFragment
6 |
7 | interface PlayableContainer {
8 |
9 | fun getItem(): T
10 |
11 | fun loadPlayables(playableFactory: PlayableFactory): List
12 |
13 | /**
14 | * Load a single Playable for this container. Only available for [AbstractSinglePlayableContainer] implementations, i.e.
15 | * containers that resolve to a single playable. Else this method always throws [UnsupportedOperationException].
16 | * See [isSinglePlayable] to check whether the method is available for any given implementation of this interface.
17 | */
18 | @Throws(UnsupportedOperationException::class)
19 | fun loadPlayable(playableFactory: PlayableFactory): Playable? {
20 | throw UnsupportedOperationException("PlayableContainer $this does not support resolving to a single Playable")
21 | }
22 |
23 | /**
24 | * Create a [QueueFragment] for this PlayableContainer, may return `null` if [loadPlayables] does not return any results.
25 | */
26 | fun createQueueFragment(playableFactory: PlayableFactory, queue: AudioQueue): QueueFragment?
27 |
28 | fun isSinglePlayable(): Boolean {
29 | return false
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/PlayableContainerManager.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables
2 |
3 | import net.robinfriedli.aiode.util.ClassDescriptorNode
4 | import org.springframework.stereotype.Component
5 |
6 | @Component
7 | open class PlayableContainerManager(private val playableContainerProviders: List>) {
8 |
9 | /**
10 | * Finds a registered [PlayableContainerProvider] for the type of the provided item. This also finds [PlayableContainerProvider]
11 | * for supertypes, casting them down to the type of of the provided item, which is safe since the type parameter exclusively
12 | * refers to the type of the item.
13 | */
14 | @Suppress("UNCHECKED_CAST")
15 | fun getPlayableContainer(item: T): PlayableContainer? {
16 | return playableContainerProviders.stream()
17 | .filter { playableContainerProvider -> playableContainerProvider.type.isAssignableFrom(item.javaClass) }
18 | .sorted(ClassDescriptorNode.getComparator())
19 | .findFirst()
20 | .map { playableContainerProvider -> (playableContainerProvider as PlayableContainerProvider).getPlayableContainer(item) }
21 | .orElse(null)
22 | }
23 |
24 | fun requirePlayableContainer(item: T): PlayableContainer {
25 | return getPlayableContainer(item)
26 | ?: throw java.util.NoSuchElementException("No playable container provider found for item of type ${item::class.java}")
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/PlayableContainerProvider.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables
2 |
3 | import net.robinfriedli.aiode.util.ClassDescriptorNode
4 |
5 | /**
6 | * Interface for singleton components that provide a [PlayableContainer] instance for an object of a specific type.
7 | */
8 | interface PlayableContainerProvider : ClassDescriptorNode {
9 |
10 | override fun getType(): Class
11 |
12 | fun getPlayableContainer(item: T): PlayableContainer
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/AudioPlaylistPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
4 | import net.robinfriedli.aiode.audio.Playable
5 | import net.robinfriedli.aiode.audio.UrlPlayable
6 | import net.robinfriedli.aiode.audio.playables.AbstractPlayableContainer
7 | import net.robinfriedli.aiode.audio.playables.PlayableContainer
8 | import net.robinfriedli.aiode.audio.playables.PlayableContainerProvider
9 | import net.robinfriedli.aiode.audio.playables.PlayableFactory
10 | import org.springframework.stereotype.Component
11 | import java.util.stream.Collectors
12 |
13 | class AudioPlaylistPlayableContainer(audioPlaylist: AudioPlaylist) : AbstractPlayableContainer(audioPlaylist) {
14 | override fun doLoadPlayables(playableFactory: PlayableFactory): List {
15 | return getItem().tracks.stream().map { track -> UrlPlayable(track) }.collect(Collectors.toList())
16 | }
17 | }
18 |
19 | @Component
20 | class AudioPlaylistPlayableContainerProvider : PlayableContainerProvider {
21 | override fun getType(): Class {
22 | return AudioPlaylist::class.java
23 | }
24 |
25 | override fun getPlayableContainer(item: AudioPlaylist): PlayableContainer {
26 | return AudioPlaylistPlayableContainer(item)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/AudioTrackPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import com.sedmelluq.discord.lavaplayer.track.AudioTrack
4 | import net.robinfriedli.aiode.audio.Playable
5 | import net.robinfriedli.aiode.audio.UrlPlayable
6 | import net.robinfriedli.aiode.audio.playables.AbstractSinglePlayableContainer
7 | import net.robinfriedli.aiode.audio.playables.PlayableContainer
8 | import net.robinfriedli.aiode.audio.playables.PlayableContainerProvider
9 | import net.robinfriedli.aiode.audio.playables.PlayableFactory
10 | import org.springframework.stereotype.Component
11 |
12 | class AudioTrackPlayableContainer(audioTrack: AudioTrack) : AbstractSinglePlayableContainer(audioTrack) {
13 | override fun doLoadPlayable(playableFactory: PlayableFactory): Playable {
14 | return UrlPlayable(getItem())
15 | }
16 | }
17 |
18 | @Component
19 | class AudioTrackPlayableContainerProvider : PlayableContainerProvider {
20 | override fun getType(): Class {
21 | return AudioTrack::class.java
22 | }
23 |
24 | override fun getPlayableContainer(item: AudioTrack): PlayableContainer {
25 | return AudioTrackPlayableContainer(item)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/PlaylistPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.playables.*
5 | import net.robinfriedli.aiode.entities.Playlist
6 | import net.robinfriedli.aiode.function.SpotifyInvoker
7 | import org.springframework.context.annotation.Lazy
8 | import org.springframework.stereotype.Component
9 |
10 | class PlaylistPlayableContainer(
11 | playlist: Playlist,
12 | private val playableContainerManager: PlayableContainerManager
13 | ) : AbstractPlayableContainer(playlist) {
14 |
15 | private val spotifyInvoker: SpotifyInvoker = SpotifyInvoker.createForCurrentContext()
16 |
17 | override fun doLoadPlayables(playableFactory: PlayableFactory): List {
18 | val items = spotifyInvoker.invokeFunction { spotifyApi -> getItem().getTracks(spotifyApi) }
19 |
20 | val playableContainers = items
21 | .stream()
22 | .map { track -> playableContainerManager.requirePlayableContainer(track) }
23 | .toList()
24 |
25 | return playableFactory.loadAll(playableContainers)
26 | }
27 | }
28 |
29 | @Component
30 | class PlaylistPlayableContainerProvider(
31 | @Lazy private val playableContainerManager: PlayableContainerManager
32 | ) : PlayableContainerProvider {
33 |
34 | override fun getType(): Class {
35 | return Playlist::class.java
36 | }
37 |
38 | override fun getPlayableContainer(item: Playlist): PlayableContainer {
39 | return PlaylistPlayableContainer(item, playableContainerManager)
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/SinglePlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.playables.AbstractSinglePlayableContainer
5 | import net.robinfriedli.aiode.audio.playables.PlayableContainer
6 | import net.robinfriedli.aiode.audio.playables.PlayableContainerProvider
7 | import net.robinfriedli.aiode.audio.playables.PlayableFactory
8 | import org.springframework.stereotype.Component
9 |
10 | class SinglePlayableContainer(playable: Playable) : AbstractSinglePlayableContainer(playable) {
11 | override fun doLoadPlayable(playableFactory: PlayableFactory): Playable {
12 | return getItem()
13 | }
14 | }
15 |
16 | @Component
17 | class SinglePlayableContainerProvider : PlayableContainerProvider {
18 | override fun getType(): Class {
19 | return Playable::class.java
20 | }
21 |
22 | override fun getPlayableContainer(item: Playable): PlayableContainer {
23 | return SinglePlayableContainer(item)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/UrlTrackPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.playables.AbstractSinglePlayableContainer
5 | import net.robinfriedli.aiode.audio.playables.PlayableContainer
6 | import net.robinfriedli.aiode.audio.playables.PlayableContainerProvider
7 | import net.robinfriedli.aiode.audio.playables.PlayableFactory
8 | import net.robinfriedli.aiode.entities.UrlTrack
9 | import org.springframework.stereotype.Component
10 |
11 | class UrlTrackPlayableContainer(urlTrack: UrlTrack) : AbstractSinglePlayableContainer(urlTrack) {
12 | override fun doLoadPlayable(playableFactory: PlayableFactory): Playable? {
13 | return getItem().asPlayable()
14 | }
15 | }
16 |
17 | @Component
18 | class UrlTrackPlayableContainerProvider : PlayableContainerProvider {
19 | override fun getType(): Class {
20 | return UrlTrack::class.java
21 | }
22 |
23 | override fun getPlayableContainer(item: UrlTrack): PlayableContainer {
24 | return UrlTrackPlayableContainer(item)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/kotlin/net/robinfriedli/aiode/audio/playables/containers/YouTubePlaylistPlayableContainer.kt:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.audio.playables.containers
2 |
3 | import net.robinfriedli.aiode.audio.Playable
4 | import net.robinfriedli.aiode.audio.playables.AbstractPlayableContainer
5 | import net.robinfriedli.aiode.audio.playables.PlayableContainer
6 | import net.robinfriedli.aiode.audio.playables.PlayableContainerProvider
7 | import net.robinfriedli.aiode.audio.playables.PlayableFactory
8 | import net.robinfriedli.aiode.audio.youtube.YouTubePlaylist
9 | import org.springframework.stereotype.Component
10 |
11 | class YouTubePlaylistPlayableContainer(youTubePlaylist: YouTubePlaylist) : AbstractPlayableContainer(youTubePlaylist) {
12 | override fun doLoadPlayables(playableFactory: PlayableFactory): List {
13 | return playableFactory.createPlayables(getItem())
14 | }
15 | }
16 |
17 | @Component
18 | class YouTubePlaylistPlayableContainerProvider : PlayableContainerProvider {
19 | override fun getType(): Class {
20 | return YouTubePlaylist::class.java
21 | }
22 |
23 | override fun getPlayableContainer(item: YouTubePlaylist): PlayableContainer {
24 | return YouTubePlaylistPlayableContainer(item)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/resources/current-version.txt:
--------------------------------------------------------------------------------
1 | 2.3.1
--------------------------------------------------------------------------------
/src/main/resources/ehcache.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
13 |
14 |
15 |
19 |
20 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %highlight(%-5level) %d{ISO8601} [%blue(%t)] %yellow(%C): %msg%n%throwable
9 |
10 |
11 |
12 |
13 | ${LOGS}/logs.log
14 |
15 | %p %d [%t] %C %m%n
16 |
17 |
18 |
19 | ${LOGS}/logs-%d{yyyy-MM-dd}.log
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/resources/quartz.properties:
--------------------------------------------------------------------------------
1 | org.quartz.scheduler.instanceName=QuartzScheduler
2 | org.quartz.threadPool.threadCount=3
3 | org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
--------------------------------------------------------------------------------
/src/main/resources/schemas/commandInterceptorSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/cronJobSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Whether this task should only run on the main instance, see application property
19 | aiode.preferences.main_instance.
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/embedDocumentSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/groovyVariableProviderSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/guildPropertySchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/guildSpecificationSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/httpHandlerSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/startupTaskSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Whether this task should only run on the main instance, see application property
17 | aiode.preferences.main_instance. Only applicable to tasks where runForEachShard is false.
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/versionSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/resources/schemas/widgetSchema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Allow multiple instances of this widget class to be active at the same time within the same guild. If false,
19 | creating a new widget of this class destroys the previous widget.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/main/resources/xml-contributions/cronJobs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/resources/xml-contributions/groovyVariableProviders.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/resources/xml-contributions/httpHandlers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/resources/xml-contributions/startupTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/webapp/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | Cargo.lock
4 |
5 | /pkg
6 | wasm-pack.log
--------------------------------------------------------------------------------
/src/main/webapp/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "webapp"
3 | version = "0.1.0"
4 | authors = ["robinfriedli "]
5 | edition = "2018"
6 | license = "Apache-2.0"
7 | description = "Web client for the botify REST API"
8 | repository = "https://github.com/robinfriedli/botify"
9 |
10 | [lib]
11 | crate-type = ["cdylib"]
12 |
13 | [dependencies]
14 | seed = "0.6.0"
15 | serde = "1"
16 | serde_json = "1"
17 | uuid = { version = "0.8", features = ["wasm-bindgen", "v4"] }
18 | wasm-bindgen = "0.2.58"
19 |
--------------------------------------------------------------------------------
/src/main/webapp/css/style.css:
--------------------------------------------------------------------------------
1 | .standard_text {
2 | font-family: "Montserrat", Arial, serif;
3 | }
4 |
5 | .white {
6 | color: white;
7 | }
8 |
9 | .green {
10 | color: #1DB954;
11 | }
12 |
13 | .green_background {
14 | background-color: #1DB954;
15 | }
16 |
17 | .grey_background {
18 | background-color: dimgrey;
19 | }
20 |
21 | .red {
22 | color: red;
23 | }
24 |
25 | .center {
26 | text-align: center;
27 | margin: 0 auto;
28 | }
29 |
30 | .left {
31 | display:inline-block;
32 | float: left;
33 | }
34 |
35 | .right {
36 | display:inline-block;
37 | float: right;
38 | }
39 |
40 | .standard_table {
41 | border-spacing: 10px;
42 | }
43 |
44 | .round {
45 | border-radius: 50%;
46 | }
47 |
48 | .no_border {
49 | border: 0 solid black;
50 | }
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-legacy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-legacy.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-small-legacy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-small-legacy.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-small.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-wide-legacy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-wide-legacy.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-wide-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-wide-transparent.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo-wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo-wide.png
--------------------------------------------------------------------------------
/src/main/webapp/img/botify-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robinfriedli/aiode/bed8fd818131c760056782bf45c1a7d354ca78a7/src/main/webapp/img/botify-logo.png
--------------------------------------------------------------------------------
/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | botify
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/webapp/src/page/home.rs:
--------------------------------------------------------------------------------
1 | use seed::{*, prelude::*};
2 |
3 | use crate::session::Client;
4 |
5 | pub type Msg = i32;
6 |
7 | pub struct Model;
8 |
9 | pub fn view(model: &Model, client: &Client) -> Node {
10 | let user = &client.user;
11 | let guild = &client.guild;
12 | let text_channel = &client.text_channel;
13 |
14 | div![
15 | h1![class!["standard_text white"],
16 | format!("Welcome {}", client.user.name)
17 | ],
18 | table![class!["standard_text standard_table white"],
19 | tr![
20 | td!["User:"],
21 | td![format!("{}", user.name)]
22 | ],
23 | tr![
24 | td!["Guild:"],
25 | td![format!("{}", guild.name)]
26 | ],
27 | tr![
28 | td!["Channel:"],
29 | td![format!("{}", text_channel.name)]
30 | ]
31 | ]
32 | ]
33 | }
--------------------------------------------------------------------------------
/src/main/webapp/src/page/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod auth;
2 | pub mod home;
--------------------------------------------------------------------------------
/src/test/java/net/robinfriedli/aiode/function/modes/RecursionPreventionModeTest.java:
--------------------------------------------------------------------------------
1 | package net.robinfriedli.aiode.function.modes;
2 |
3 | import org.testng.annotations.*;
4 |
5 | import net.robinfriedli.exec.BaseInvoker;
6 | import net.robinfriedli.exec.Mode;
7 |
8 | public class RecursionPreventionModeTest {
9 |
10 | @Test
11 | public void testNoRecurse() {
12 | RecursionPreventionMode recursionPreventionMode = new RecursionPreventionMode("test");
13 |
14 | new BaseInvoker().invoke(Mode.create().with(recursionPreventionMode), this::testNoRecurse);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------