Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.api.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DATABASE_URL=postgresql://postgres:password@localhost:5432/cafeBot?schema=public
KAWAII_API_TOKEN=
NODE_ENV=bot_testing
5 changes: 5 additions & 0 deletions .env.web.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DATABASE_URL=postgresql://postgres:password@localhost:5432/cafeBot?schema=public
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=THISISANOTHERSECRETTHATMUSTBE32CHARACTERSLONGATLEASTFORSOMEREASON
BETTER_AUTH_DISCORD_ID=
BETTER_AUTH_DISCORD_SECRET=
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ gradle.properties

# Files containing safety info
.env
.env.api
.env.web
beta.json
release.json

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Commands are separated by sections. Each section has a different set of commands
* `/twitch` - Add or remove twitch channels to be notified for!
* `/version` - Get the current bot version!
* `/who` - Get some information about yourself or another user!
* `/calendar` - Add, view, and parse calendars. You can now view your calendars through Discord!

#### 5. Interaction Commands

Expand Down
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ allprojects {
archiveVersion.set(project.version as String)
println("Compiling: " + project.name + "-" + project.version + ".jar")
}

mergeServiceFiles()
}

tasks.test {
Expand Down Expand Up @@ -105,6 +107,8 @@ dependencies {
implementation("org.apache.logging.log4j:log4j-core:2.25.3")
implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3") // JDA logging.

implementation("org.mnode.ical4j:ical4j:4.2.3") // Calendar Stuff - https://mvnrepository.com/artifact/org.mnode.ical4j/ical4j

implementation("com.github.twitch4j:twitch4j:1.25.0") // Twitch - https://github.com/twitch4j/twitch4j

compileOnly("org.projectlombok:lombok:1.18.42")
Expand All @@ -118,7 +122,12 @@ tasks.withType<ShadowJar> {
exclude(dependency("io.github.xanthic.cache:.*:.*"))
exclude(dependency("com.github.twitch4j:.*:.*"))
exclude(dependency("com.squareup.okhttp3:.*:.*"))
exclude(dependency("org.mnode.ical4j:.*:.*"))
}

relocate("org.mnode.ical4j", "com.beanbeanjuice.libs.org.mnode.ical4j")

mergeServiceFiles()
}

configure<ProcessResources>("processResources") {
Expand Down
68 changes: 68 additions & 0 deletions docker-compose.dry-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: cafeBot

services:
db:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: cafeBot
ports:
- "5432:5432"
volumes:
- cafeBot_data:/var/lib/postgresql/18/docker
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d cafeBot"]
interval: 5s
timeout: 5s
retries: 5

api:
image: git.beanbeanjuice.dev/beanbeanjuice/cafebot-api:dev-latest
env_file:
- .env.api
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:5000/api/v4/hello" ]
interval: 10s
timeout: 5s
retries: 20
start_period: 20s
environment:
DATABASE_URL: postgres://postgres:password@db:5432/cafeBot # Override env file.
NODE_ENV: bot_testing

web:
image: git.beanbeanjuice.dev/beanbeanjuice/cafebot-web:dev-latest
env_file:
- .env.web
ports:
- "3000:3000"
depends_on:
api:
condition: service_healthy
restart: unless-stopped
environment:
DATABASE_URL: postgres://postgres:password@db:5432/cafeBot # Override env file.

bot:
build:
context: .
env_file:
- .env
depends_on:
api:
condition: service_healthy
volumes:
- ./logs:/bot/logs
environment:
CAFEBOT_LOG_LEVEL: INFO
CAFEBOT_API_URL: http://api:5000
restart: unless-stopped

volumes:
cafeBot_data:
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.beanbeanjuice.cafebot.api.wrapper;

import com.beanbeanjuice.cafebot.api.wrapper.api.discord.CalendarApi;
import com.beanbeanjuice.cafebot.api.wrapper.api.discord.MenuApi;
import com.beanbeanjuice.cafebot.api.wrapper.api.discord.generic.BotSettingsApi;
import com.beanbeanjuice.cafebot.api.wrapper.api.discord.server.*;
Expand All @@ -22,6 +23,7 @@ public class CafeAPI {

// Discord APIs
private final MenuApi menuApi;
private final CalendarApi calendarApi;

// User APIs
private final BirthdayApi birthdayApi;
Expand Down Expand Up @@ -51,12 +53,15 @@ public class CafeAPI {
* @see <a href="https://docs.kawaii.red/">Kawaii API Documentation</a>
*/
public CafeAPI(String baseUrl, String token) {
Runtime.getRuntime().addShutdownHook(new Thread(RequestBuilder::shutdown));

// General APIs
this.botSettingsApi = new BotSettingsApi(baseUrl, token);
this.greetingApi = new GreetingApi(baseUrl, token);

// Discord APIs
this.menuApi = new MenuApi(baseUrl, token);
this.calendarApi = new CalendarApi(baseUrl, token);

// User APIs
this.birthdayApi = new BirthdayApi(baseUrl, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ public class RequestBuilder {
private Method method;
private Object body;

private static final CloseableHttpAsyncClient CLIENT;

static {
CLIENT = HttpAsyncClients.custom().build();
CLIENT.start();
}

public static void shutdown() {
try {
CLIENT.close();
} catch (Exception e) {
// log if needed
}
}

public static RequestBuilder builder() {
return new RequestBuilder();
}
Expand Down Expand Up @@ -65,9 +80,6 @@ public CompletableFuture<BasicResponse> queue() throws URISyntaxException {
.setPath(route)
.build();

CloseableHttpAsyncClient client = HttpAsyncClients.custom().build();
client.start();

JsonMapper mapper = new JsonMapper();
CompletableFuture<BasicResponse> future = new CompletableFuture<>();

Expand All @@ -77,12 +89,11 @@ public CompletableFuture<BasicResponse> queue() throws URISyntaxException {
request.setBody(json, ContentType.APPLICATION_JSON);
} catch (Exception e) {
future.completeExceptionally(e);
closeClient(client);
return future;
}
}

client.execute(request, new FutureCallback<>() {
CLIENT.execute(request, new FutureCallback<>() {
@Override
public void completed(SimpleHttpResponse response) {
try {
Expand All @@ -108,39 +119,27 @@ public void completed(SimpleHttpResponse response) {
jsonNode.has("error") ? jsonNode.get("error").toPrettyString() : jsonNode.toPrettyString())
)
);
return;
}

future.complete(new BasicResponse(statusCode, jsonNode));
} catch (Exception e) {
future.completeExceptionally(e);
} finally {
closeClient(client);
}
}

@Override
public void failed(Exception ex) {
future.completeExceptionally(ex);
closeClient(client);
}

@Override
public void cancelled() {
future.cancel(true);
closeClient(client);
}
});

return future;
}

private void closeClient(CloseableHttpAsyncClient client) {
try {
client.close();
} catch (Exception e) {
// Log or ignore
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.beanbeanjuice.cafebot.api.wrapper.api.discord;

import com.beanbeanjuice.cafebot.api.wrapper.RequestBuilder;
import com.beanbeanjuice.cafebot.api.wrapper.api.Api;
import com.beanbeanjuice.cafebot.api.wrapper.api.enums.OwnerType;
import com.beanbeanjuice.cafebot.api.wrapper.response.BasicResponse;
import com.beanbeanjuice.cafebot.api.wrapper.type.calendar.Calendar;
import com.beanbeanjuice.cafebot.api.wrapper.type.calendar.PartialCalendar;
import org.apache.hc.core5.http.Method;
import tools.jackson.databind.JsonNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class CalendarApi extends Api {

public CalendarApi(String baseUrl, String token) {
super(baseUrl, token);
}

public CompletableFuture<Calendar> getCalendar(String calendarId) {
try {
return RequestBuilder.builder()
.method(Method.GET)
.baseUrl(baseUrl)
.token(token)
.route(String.format("/api/v4/discord/calendars/%s", calendarId))
.queue()
.thenApply(BasicResponse::getBody)
.thenApply((node) -> node.get("calendar"))
.thenApply(this::parseCalendar);
} catch (Exception e) {
throw new RuntimeException("Invalid route: " + e.getMessage());
}
}

public CompletableFuture<List<Calendar>> getGuildCalendars(String guildId) {
try {
return RequestBuilder.builder()
.method(Method.GET)
.baseUrl(baseUrl)
.token(token)
.route(String.format("/api/v4/discord/calendars/guild/%s", guildId))
.queue()
.thenApply(BasicResponse::getBody)
.thenApply((node) -> node.get("calendars"))
.thenApply(this::parseCalendars);
} catch (Exception e) {
throw new RuntimeException("Invalid route: " + e.getMessage());
}
}

public CompletableFuture<List<Calendar>> getUserCalendars(String userId) {
try {
return RequestBuilder.builder()
.method(Method.GET)
.baseUrl(baseUrl)
.token(token)
.route(String.format("/api/v4/discord/calendars/user/%s", userId))
.queue()
.thenApply(BasicResponse::getBody)
.thenApply((node) -> node.get("calendars"))
.thenApply(this::parseCalendars);
} catch (Exception e) {
throw new RuntimeException("Invalid route: " + e.getMessage());
}
}

public CompletableFuture<Calendar> createCalendar(PartialCalendar calendar) {
Map<String, String> body = new HashMap<>();

body.put("ownerId", calendar.getOwnerId());
body.put("ownerType", calendar.getOwnerType().toString());
body.put("name", calendar.getName());
body.put("url", calendar.getUrl());

try {
return RequestBuilder.builder()
.method(Method.POST)
.baseUrl(baseUrl)
.token(token)
.route("/api/v4/discord/calendars")
.body(body)
.queue()
.thenApply(BasicResponse::getBody)
.thenApply((node) -> node.get("calendar"))
.thenApply(this::parseCalendar);
} catch (Exception e) {
throw new RuntimeException("Invalid route: " + e.getMessage());
}
}

public CompletableFuture<Void> deleteCalendar(String calendarId, String callerId) {
try {
return RequestBuilder.builder()
.method(Method.DELETE)
.baseUrl(baseUrl)
.token(token)
.route(String.format("/api/v4/discord/calendars/%s?callerId=%s", calendarId, callerId))
.queue()
.thenApply((res) -> null);
} catch (Exception e) {
throw new RuntimeException("Invalid route: " + e.getMessage());
}
}

private Calendar parseCalendar(JsonNode calendarNode) {
String id = calendarNode.get("id").asString();
OwnerType ownerType = OwnerType.valueOf(calendarNode.get("ownerType").asString());
String ownerId = (ownerType == OwnerType.DISCORD_USER) ? calendarNode.get("discordUserId").asString() : calendarNode.get("guildId").asString();

String name = calendarNode.get("name").asString();
String url = calendarNode.get("url").asString();

return new Calendar(id, ownerType, ownerId, name, url);
}

private List<Calendar> parseCalendars(JsonNode calendarsNode) {
List<Calendar> calendars = new ArrayList<>();

for (JsonNode calendarNode : calendarsNode) calendars.add(parseCalendar(calendarNode));

return calendars;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.beanbeanjuice.cafebot.api.wrapper.api.enums;

public enum OwnerType {

USER,
DISCORD_USER,
GUILD

}
Loading
Loading