Compare commits

...

17 Commits

Author SHA1 Message Date
Sheldan
fd70e6ac90 [maven-release-plugin] prepare release abstracto-application-1.4.25 2023-05-19 00:27:02 +02:00
Sheldan
29bde70796 [AB-93] fixing setting attached files to null 2023-05-19 00:24:45 +02:00
Sheldan
ecd4feabb2 [maven-release-plugin] prepare for next development iteration 2023-05-18 23:45:59 +02:00
Sheldan
abf60409f1 [maven-release-plugin] prepare release abstracto-application-1.4.24 2023-05-18 23:45:55 +02:00
Sheldan
080733957f [AB-93] fixing not initializing the attached files 2023-05-18 23:43:34 +02:00
Sheldan
8a41f366ae [maven-release-plugin] prepare for next development iteration 2023-05-18 22:53:56 +02:00
Sheldan
dbf478c44c [maven-release-plugin] prepare release abstracto-application-1.4.23 2023-05-18 22:53:52 +02:00
Sheldan
3df688571f [AB-93] changing how MessageToSend handles attached plaintext files 2023-05-18 22:32:56 +02:00
Sheldan
ca530949c6 [AB-92] fixing not providing member display object for modmail thread already exists embed 2023-05-18 01:23:42 +02:00
Sheldan
724930e5a4 [AB-xxx] updating readme
upgrading JDA version to 5.0.0-beta.5
2023-03-20 23:16:52 +01:00
Sheldan
2875da117a [AB-91] adding location id to the available information for weather model 2023-03-19 10:54:54 +01:00
Sheldan
f95ba6c28f [maven-release-plugin] prepare for next development iteration 2023-03-19 00:14:21 +01:00
Sheldan
1b2e7654f9 [maven-release-plugin] prepare release abstracto-application-1.4.22 2023-03-19 00:14:16 +01:00
Sheldan
54976ed1d4 [AB-89] adding command to retrieve weather data 2023-03-19 00:09:43 +01:00
Sheldan
735816f5dd [AB-xxx] adding feature to split field value by configurable amount
using larger images for member avatar
2023-03-05 13:49:32 +01:00
Sheldan
a984bdb84e [AB-86] fixing exception in case of a re-joining user without experience role 2023-02-26 14:16:27 +01:00
Sheldan
21add6585d [maven-release-plugin] prepare for next development iteration 2023-02-26 10:58:01 +01:00
119 changed files with 842 additions and 250 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Sheldan
Copyright (c) 2023 Sheldan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -14,22 +14,20 @@ An example implementation of this bot can be seen [here](https://github.com/Shel
## Technologies
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 5.0.0-alpha.21
* [Spring boot](https://github.com/spring-projects/spring-boot) is used as a framework to create standalone application in Java with Java EE methods. (including Dependency injection and more)
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper in the version 5.0.0-beta.5
* [Spring boot](https://github.com/spring-projects/spring-boot) is used as a framework to create standalone application in Java with Java EE methods. (including dependency injection and more)
* [Hibernate](https://github.com/hibernate/hibernate-orm) is used as a reference implementation of JPA.
* [Freemarker](https://github.com/apache/freemarker) is used as a templating engine. This is used to provide internationalization for user facing text and enable dynamic embed configuration.
* [Ehcache](https://github.com/ehcache/ehcache3) is used as a caching implementation.
* [Lombok](https://github.com/rzwitserloot/lombok) is used as a framework in order to speed up creation of container classes and builders.
* [Quartz](https://github.com/quartz-scheduler/quartz) is used as a scheduling framework in order to provide functionalities which either require a delayed or cronjob behaviour.
* [Docker](https://github.com/docker) is used to package the application into a container and [Docker Compose](https://github.com/docker/compose) is used to connect the required containers together.
* [Quartz](https://github.com/quartz-scheduler/quartz) is used as a scheduling framework in order to provide functionalities which either require a scheduled or cronjob behaviour.
* [Docker](https://github.com/docker) is used to package the application into an image and [Docker Compose](https://github.com/docker/compose) is used to build the images
* [Liquibase](https://github.com/liquibase/liquibase) is used to manage changes to the database
## Documentation
A detailed documentation of the pure form of Abstracto including the terminology and commands in HTML form is available [here](https://sheldan.github.io/abstracto-docs/current). The PDF is available [here](https://sheldan.github.io/abstracto-docs/current/documentation.pdf)
If you want to view the documentation to an earlier released version you need to append the desired version to the URL. The current version will be available aforementioned URL, but it is not right now, because Abstracto has not been released yet.
## Customization documentation
TBD when Abstracto is released, as the current version is still being adapted, because of findings from the example customization in Crimson.
## Issues
If you find any issue, feel free to create a GitHub issue.

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>anti-raid</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assignable-roles-int</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>custom-command</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>dynamic-activity</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>entertainment</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -275,13 +275,13 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
CompletableFuture<Void> returningFuture;
if(!Objects.equals(oldRoleId, newRoleId) || forceRoles) {
CompletableFuture<Void> addingFuture;
if(oldRoleId != null || forceRoles) {
if(oldRoleId != null) {
addingFuture = roleService.removeRoleFromMemberAsync(member, oldRoleId);
} else {
addingFuture = CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> removingFeature;
if(newRoleId != null || forceRoles) {
if(newRoleId != null) {
removingFeature = roleService.addRoleToMemberAsync(member, newRoleId);
} else {
removingFeature = CompletableFuture.completedFuture(null);

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>invite-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>link-embed</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>logging</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -12,6 +12,7 @@ import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.EntityGuildMismatchException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberNameDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
@@ -81,6 +82,7 @@ public class Contact extends AbstractConditionableCommand {
ModMailThreadExistsModel model = ModMailThreadExistsModel
.builder()
.existingModMailThread(existingThread)
.executingMemberDisplay(MemberNameDisplay.fromMember(commandContext.getAuthor()))
.build();
List<CompletableFuture<Message>> futures = channelService.sendEmbedTemplateInTextChannelList(MODMAIL_THREAD_ALREADY_EXISTS_TEMPLATE, model, commandContext.getChannel());
return FutureUtils.toSingleFutureGeneric(futures).thenApply(aVoid -> CommandResult.fromIgnored());
@@ -106,6 +108,7 @@ public class Contact extends AbstractConditionableCommand {
ModMailThreadExistsModel model = ModMailThreadExistsModel
.builder()
.existingModMailThread(existingThread)
.executingMemberDisplay(MemberNameDisplay.fromMember(event.getMember()))
.build();
return interactionService.replyEmbed(MODMAIL_THREAD_ALREADY_EXISTS_TEMPLATE, model, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,5 +1,6 @@
package dev.sheldan.abstracto.modmail.model.template;
import dev.sheldan.abstracto.core.models.template.display.MemberNameDisplay;
import dev.sheldan.abstracto.core.utils.ChannelUtils;
import dev.sheldan.abstracto.modmail.model.database.ModMailThread;
import lombok.Builder;
@@ -15,6 +16,7 @@ import lombok.Setter;
@Builder
public class ModMailThreadExistsModel {
private ModMailThread existingModMailThread;
private MemberNameDisplay executingMemberDisplay;
public String getThreadUrl() {
return ChannelUtils.buildChannelUrl(existingModMailThread.getServer().getId(), existingModMailThread.getChannel().getId());

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>profanity-filter</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -249,6 +249,7 @@ public class RemindServiceBeanTest {
when(guild.getIdLong()).thenReturn(8L);
when(remindedMember.getIdLong()).thenReturn(9L);
when(remindedMember.getUser()).thenReturn(jdaUser);
when(remindedMember.getEffectiveAvatar()).thenReturn(Mockito.mock(ImageProxy.class));
when(jdaUser.getDefaultAvatar()).thenReturn(Mockito.mock(ImageProxy.class));
Reminder remindedReminder = Mockito.mock(Reminder.class);
when(remindedReminder.getTargetDate()).thenReturn(Instant.now());

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>remind</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>repost-detection</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>starboard</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -7,7 +7,6 @@ import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.UploadFileTooLargeException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
@@ -25,8 +24,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@@ -46,8 +43,6 @@ import java.util.concurrent.CompletableFuture;
public class ExportEmoteStats extends AbstractConditionableCommand {
public static final String DOWNLOAD_EMOTE_STATS_NO_STATS_AVAILABLE_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_no_stats_available_response";
public static final String DOWNLOAD_EMOTE_STATS_FILE_NAME_TEMPLATE_KEY = "downloadEmoteStats_file_name";
public static final String DOWNLOAD_EMOTE_STATS_FILE_CONTENT_TEMPLATE_KEY = "downloadEmoteStats_file_content";
public static final String DOWNLOAD_EMOTE_STATS_RESPONSE_TEMPLATE_KEY = "downloadEmoteStats_response";
@Autowired
private ServerManagementService serverManagementService;
@@ -93,30 +88,10 @@ public class ExportEmoteStats extends AbstractConditionableCommand {
.requester(commandContext.getAuthor())
.statsSince(toUseForModel)
.build();
String fileName = templateService.renderTemplate(DOWNLOAD_EMOTE_STATS_FILE_NAME_TEMPLATE_KEY, model);
String fileContent = templateService.renderTemplate(DOWNLOAD_EMOTE_STATS_FILE_CONTENT_TEMPLATE_KEY, model);
model.setEmoteStatsFileName(fileName);
File tempFile = fileService.createTempFile(fileName);
try {
fileService.writeContentToFile(tempFile, fileContent);
long maxFileSize = commandContext.getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(DOWNLOAD_EMOTE_STATS_RESPONSE_TEMPLATE_KEY, model, actualServer.getId());
messageToSend.getAttachedFiles().get(0).setFile(tempFile.getAbsoluteFile());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromIgnored());
} catch (IOException e) {
throw new AbstractoRunTimeException(e);
} finally {
try {
fileService.safeDelete(tempFile);
} catch (IOException e) {
log.error("Failed to delete temporary export emote statistics file {}.", tempFile.getAbsoluteFile(), e);
}
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(DOWNLOAD_EMOTE_STATS_RESPONSE_TEMPLATE_KEY, model, actualServer.getId());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(unused -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromIgnored());
}
@Override

View File

@@ -2,8 +2,6 @@ package dev.sheldan.abstracto.statistic.emote.command;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.UploadFileTooLargeException;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
@@ -14,7 +12,6 @@ import dev.sheldan.abstracto.statistic.config.StatisticFeatureDefinition;
import dev.sheldan.abstracto.statistic.emote.model.DownloadEmoteStatsModel;
import dev.sheldan.abstracto.statistic.emote.model.database.UsedEmote;
import dev.sheldan.abstracto.statistic.emote.service.management.UsedEmoteManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import org.junit.Assert;
import org.junit.Test;
@@ -22,11 +19,8 @@ import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -77,53 +71,11 @@ public class ExportEmoteStatsTest {
verify(channelService, times(1)).sendEmbedTemplateInTextChannelList(eq(DOWNLOAD_EMOTE_STATS_NO_STATS_AVAILABLE_RESPONSE_TEMPLATE_KEY), any(), eq(commandContext.getChannel()));
}
@Test(expected = AbstractoRunTimeException.class)
public void testFileIOException() throws IOException {
CommandContext commandContext = CommandTestUtilities.getNoParameters();
mockServerAndFileRendering(commandContext);
File file = Mockito.mock(File.class);
when(fileService.createTempFile(FILE_NAME)).thenReturn(file);
doThrow(new IOException()).when(fileService).writeContentToFile(file, FILE_CONTENT);
testUnit.executeAsync(commandContext);
}
@Test(expected = UploadFileTooLargeException.class)
public void testExportAllEmoteStatsTooBig() throws IOException {
CommandContext commandContext = CommandTestUtilities.getNoParameters();
when(commandContext.getGuild().getMaxFileSize()).thenReturn(2L);
mockServerAndFileRendering(commandContext);
File file = Mockito.mock(File.class);
when(fileService.createTempFile(FILE_NAME)).thenReturn(file);
when(file.length()).thenReturn(3L);
MessageToSend messageToSend = Mockito.mock(MessageToSend.class);
CompletableFuture<CommandResult> asyncResult = testUnit.executeAsync(commandContext);
CommandTestUtilities.checkSuccessfulCompletionAsync(asyncResult);
verify(fileService, times(1)).writeContentToFile(file, FILE_CONTENT);
verify(fileService, times(1)).safeDelete(file);
verifyModel();
}
@Test
public void testFeature() {
Assert.assertEquals(StatisticFeatureDefinition.EMOTE_TRACKING, testUnit.getFeature());
}
private void verifyModel() {
DownloadEmoteStatsModel model = modelArgumentCaptor.getValue();
Assert.assertEquals(1, model.getEmotes().size());
Assert.assertEquals(usedEmote, model.getEmotes().get(0));
}
private void mockServerAndFileRendering(CommandContext commandContext) {
when(commandContext.getGuild().getIdLong()).thenReturn(SERVER_ID);
AServer server = Mockito.mock(AServer.class);
when(serverManagementService.loadServer(SERVER_ID)).thenReturn(server);
List<UsedEmote> usedEmotes = Arrays.asList(usedEmote);
when(usedEmoteManagementService.loadEmoteUsagesForServerSince(server, Instant.EPOCH)).thenReturn(usedEmotes);
when(templateService.renderTemplate(eq(DOWNLOAD_EMOTE_STATS_FILE_NAME_TEMPLATE_KEY), modelArgumentCaptor.capture())).thenReturn(FILE_NAME);
when(templateService.renderTemplate(eq(DOWNLOAD_EMOTE_STATS_FILE_CONTENT_TEMPLATE_KEY), any())).thenReturn(FILE_CONTENT);
}
@Test
public void validateCommand() {
CommandConfigValidator.validateCommandConfiguration(testUnit.getConfiguration());

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>suggestion</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>utility</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>abstracto-modules</artifactId>
<groupId>dev.sheldan.abstracto.modules</groupId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>abstracto-modules</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,177 @@
package dev.sheldan.abstracto.webservices.openeweathermap.command;
import dev.sheldan.abstracto.core.command.UtilityModuleDefinition;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.abstracto.webservices.config.WebServicesSlashCommandNames;
import dev.sheldan.abstracto.webservices.config.WebserviceFeatureDefinition;
import dev.sheldan.abstracto.webservices.openweathermap.config.OpenWeatherMapConfig;
import dev.sheldan.abstracto.webservices.openweathermap.exception.LocationNotFoundException;
import dev.sheldan.abstracto.webservices.openweathermap.model.*;
import dev.sheldan.abstracto.webservices.openweathermap.service.OpenWeatherMapService;
import dev.sheldan.abstracto.webservices.openweathermap.service.WeatherService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import org.checkerframework.checker.index.qual.SameLen;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class OpenWeatherMap extends AbstractConditionableCommand {
private static final String OPEN_WEATHER_MAP_COMMAND = "openWeatherMap";
private static final String SEARCH_QUERY_PARAMETER = "searchQuery";
private static final String OPEN_WEATHER_MAP_RESPONSE_TEMPLATE_KEY = "openWeatherMap_command_response";
@Autowired
private SlashCommandParameterService slashCommandParameterService;
@Autowired
private InteractionService interactionService;
@Autowired
private ChannelService channelService;
@Autowired
private OpenWeatherMapService openWeatherMapService;
@Autowired
private TemplateService templateService;
@Autowired
private ConfigService configService;
@Autowired
private WeatherService weatherService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String parameter = (String) commandContext.getParameters().getParameters().get(0);
MessageToSend message = getMessageToSend(commandContext.getGuild().getIdLong(), parameter);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(message, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String query = slashCommandParameterService.getCommandOption(SEARCH_QUERY_PARAMETER, event, String.class);
MessageToSend messageToSend = getMessageToSend(event.getGuild().getIdLong(), query);
return interactionService.replyMessageToSend(messageToSend, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
private MessageToSend getMessageToSend(Long serverId, String parameter) {
try {
GeoCodingResult geoCodingResult = openWeatherMapService.searchForLocation(parameter);
if(geoCodingResult.getResults().isEmpty()) {
throw new LocationNotFoundException();
}
String languageKey = configService.getStringValueOrConfigDefault(OpenWeatherMapConfig.OPEN_WEATHER_MAP_LANGUAGE_KEY_SYSTEM_CONFIG_KEY, serverId);
GeoCodingLocation chosenLocation = geoCodingResult.getResults().get(0);
WeatherResult weatherResult = openWeatherMapService.retrieveWeatherForLocation(chosenLocation, languageKey);
WeatherResponseModel.WeatherResponseModelBuilder builder = WeatherResponseModel
.builder()
.description(weatherResult.getWeathers() != null && !weatherResult.getWeathers().isEmpty()
? weatherResult.getWeathers().get(0).getDescription() : null)
.mainWeather(weatherResult.getWeathers() != null && !weatherResult.getWeathers().isEmpty()
? weatherResult.getWeathers().get(0).getMain() : null)
.clouds(weatherResult.getCloudInfo() != null ? weatherResult.getCloudInfo().getAll() : null)
.rain1H(weatherResult.getRainInfo() != null ? weatherResult.getRainInfo().getRain1H() : null)
.rain3H(weatherResult.getRainInfo() != null ? weatherResult.getRainInfo().getRain3H() : null)
.snow1H(weatherResult.getSnowInfo() != null ? weatherResult.getSnowInfo().getSnow1H() : null)
.snow3H(weatherResult.getSnowInfo() != null ? weatherResult.getSnowInfo().getSnow3H() : null)
.visibility(weatherResult.getVisibility())
.locationName(chosenLocation.getName())
.countryKey(chosenLocation.getCountryKey())
.dataCalculationTime(weatherResult.getDayTime() != null ? Instant.ofEpochSecond(weatherResult.getDayTime()) : null);
Color embedColor = null;
WeatherResultMain mainWeather = weatherResult.getMainWeather();
if(mainWeather != null) {
builder.feelsLikeTemperature(mainWeather.getFeelsLikeTemperature())
.temperature(mainWeather.getTemperature())
.maxTemperature(mainWeather.getMaxTemperature())
.minTemperature(mainWeather.getMinTemperature())
.pressure(mainWeather.getPressure())
.seaLevelPressure(mainWeather.getSeaLevelPressure())
.groundLevelPressure(mainWeather.getGroundLevelPressure())
.humidity(mainWeather.getHumidity());
embedColor = weatherService.getColorForTemperature(mainWeather.getFeelsLikeTemperature());
}
builder.embedColor(embedColor);
WeatherResultSystem systemInfo = weatherResult.getSystemInfo();
if(systemInfo != null) {
builder.sunset(systemInfo.getSunset() != null ? Instant.ofEpochSecond(systemInfo.getSunset()) : null);
builder.sunset(systemInfo.getSunrise() != null ? Instant.ofEpochSecond(systemInfo.getSunrise()) : null);
}
builder.locationId(weatherResult.getLocationId());
return templateService.renderEmbedTemplate(OPEN_WEATHER_MAP_RESPONSE_TEMPLATE_KEY, builder.build(), serverId);
} catch (IOException e) {
log.warn("Failed to load weather in server {}", serverId, e);
throw new AbstractoRunTimeException(e);
}
}
@Override
public FeatureDefinition getFeature() {
return WebserviceFeatureDefinition.OPEN_WEATHER_MAP;
}
@Override
public CommandConfiguration getConfiguration() {
List<Parameter> parameters = new ArrayList<>();
Parameter searchQueryParameter = Parameter
.builder()
.name(SEARCH_QUERY_PARAMETER)
.type(String.class)
.remainder(true)
.templated(true)
.build();
parameters.add(searchQueryParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
SlashCommandConfig slashCommandConfig = SlashCommandConfig
.builder()
.enabled(true)
.rootCommandName(WebServicesSlashCommandNames.WEATHER)
.commandName("search")
.build();
return CommandConfiguration.builder()
.name(OPEN_WEATHER_MAP_COMMAND)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.supportsEmbedException(true)
.causesReaction(false)
.parameters(parameters)
.help(helpInfo)
.build();
}
}

View File

@@ -0,0 +1,63 @@
package dev.sheldan.abstracto.webservices.openeweathermap.service;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingLocation;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingResult;
import dev.sheldan.abstracto.webservices.openweathermap.model.WeatherResult;
import dev.sheldan.abstracto.webservices.openweathermap.service.OpenWeatherMapService;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@Component
public class OpenWeatherMapServiceBean implements OpenWeatherMapService {
@Autowired
private OkHttpClient okHttpClient;
@Value("${abstracto.feature.webservices.openweatherMap.apiKey}")
private String apiKey;
@Value("${abstracto.feature.webservices.openweathermap.geocodingURL}")
private String geoCodingURL;
@Value("${abstracto.feature.webservices.openweathermap.weatherDataURL}")
private String weatherDataURL;
@Autowired
private Gson gson;
@Override
public GeoCodingResult searchForLocation(String query) throws IOException {
Type geoCodingType = new TypeToken<ArrayList<GeoCodingLocation>>() {}.getType();
Request request = new Request.Builder()
.url(String.format(geoCodingURL, query, 5, apiKey))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
List<GeoCodingLocation> result = gson.fromJson(response.body().string(), geoCodingType);
return GeoCodingResult
.builder()
.results(result)
.build();
}
@Override
public WeatherResult retrieveWeatherForLocation(GeoCodingLocation location, String languageKey) throws IOException {
Request request = new Request.Builder()
.url(String.format(weatherDataURL, location.getLatitude(), location.getLongitude(), apiKey, languageKey))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
return gson.fromJson(response.body().string(), WeatherResult.class);
}
}

View File

@@ -0,0 +1,67 @@
package dev.sheldan.abstracto.webservices.openeweathermap.service;
import dev.sheldan.abstracto.webservices.openweathermap.service.WeatherService;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
@Component
public class WeatherServiceBean implements WeatherService {
private static final Map<Pair<Integer, Integer>, String> TEMPERATURE_COLOR_MAP = new HashMap<>();
// source for colors: https://www.esri.com/arcgis-blog/products/arcgis-pro/mapping/a-meaningful-temperature-palette/
static {
TEMPERATURE_COLOR_MAP.put(Pair.of(-100, -48), "#E4EFFF");
TEMPERATURE_COLOR_MAP.put(Pair.of(-48, -45), "#DCE9FA");
TEMPERATURE_COLOR_MAP.put(Pair.of(-45, -42), "#D3E2F7");
TEMPERATURE_COLOR_MAP.put(Pair.of(-42, -40), "#CBDBF4");
TEMPERATURE_COLOR_MAP.put(Pair.of(-40, -37), "#C0D4ED");
TEMPERATURE_COLOR_MAP.put(Pair.of(-37, -34), "#B8CDEA");
TEMPERATURE_COLOR_MAP.put(Pair.of(-34, -31), "#AFC6E6");
TEMPERATURE_COLOR_MAP.put(Pair.of(-31, -28), "#A7BFE3");
TEMPERATURE_COLOR_MAP.put(Pair.of(-28, -26), "#9CB8DF");
TEMPERATURE_COLOR_MAP.put(Pair.of(-26, -23), "#93B1D6");
TEMPERATURE_COLOR_MAP.put(Pair.of(-23, -20), "#89A4CD");
TEMPERATURE_COLOR_MAP.put(Pair.of(-20, -17), "#7F9BC3");
TEMPERATURE_COLOR_MAP.put(Pair.of(-17, -15), "#7590B9");
TEMPERATURE_COLOR_MAP.put(Pair.of(-15, -12), "#617AA8");
TEMPERATURE_COLOR_MAP.put(Pair.of(-12, -9), "#56719C");
TEMPERATURE_COLOR_MAP.put(Pair.of(-9, -6), "#4D6591");
TEMPERATURE_COLOR_MAP.put(Pair.of(-6, -3), "#415C87");
TEMPERATURE_COLOR_MAP.put(Pair.of(-3, -1), "#39517F");
TEMPERATURE_COLOR_MAP.put(Pair.of(-1, 1), "#2F4577");
TEMPERATURE_COLOR_MAP.put(Pair.of(1, 4), "#26436F");
TEMPERATURE_COLOR_MAP.put(Pair.of(4, 7), "#254F77");
TEMPERATURE_COLOR_MAP.put(Pair.of(7, 10), "#275B80");
TEMPERATURE_COLOR_MAP.put(Pair.of(10, 12), "#27678A");
TEMPERATURE_COLOR_MAP.put(Pair.of(12, 15), "#287593");
TEMPERATURE_COLOR_MAP.put(Pair.of(15, 18), "#438190");
TEMPERATURE_COLOR_MAP.put(Pair.of(18, 21), "#648C89");
TEMPERATURE_COLOR_MAP.put(Pair.of(21, 23), "#879A84");
TEMPERATURE_COLOR_MAP.put(Pair.of(23, 26), "#ABA87D");
TEMPERATURE_COLOR_MAP.put(Pair.of(26, 29), "#C2A875");
TEMPERATURE_COLOR_MAP.put(Pair.of(29, 32), "#C19D61");
TEMPERATURE_COLOR_MAP.put(Pair.of(32, 35), "#C38A53");
TEMPERATURE_COLOR_MAP.put(Pair.of(35, 37), "#BE704C");
TEMPERATURE_COLOR_MAP.put(Pair.of(37, 40), "#AF4D4C");
TEMPERATURE_COLOR_MAP.put(Pair.of(40, 43), "#9F294C");
TEMPERATURE_COLOR_MAP.put(Pair.of(43, 46), "#87203E");
TEMPERATURE_COLOR_MAP.put(Pair.of(46, 48), "#631531");
TEMPERATURE_COLOR_MAP.put(Pair.of(48, 65), "#560C25");
TEMPERATURE_COLOR_MAP.put(Pair.of(65, 100), "#3D0216");
}
@Override
public Color getColorForTemperature(Float temperature) {
return Color.decode(TEMPERATURE_COLOR_MAP.get(TEMPERATURE_COLOR_MAP
.keySet()
.stream()
.filter(pair -> pair.getLeft() < temperature && pair.getRight() > temperature)
.findFirst().orElse(TEMPERATURE_COLOR_MAP.keySet().iterator().next())));
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd">
<include file="seedData/data.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,20 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<property name="openWeatherMapFeature" value="(SELECT id FROM feature WHERE key = 'openWeatherMap')"/>
<changeSet author="Sheldan" id="openWeatherMap-command">
<insert tableName="command">
<column name="name" value="openWeatherMap"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${openWeatherMapFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="feature.xml" relativeToChangelogFile="true"/>
<include file="command.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="openWeatherMap_feature-insertion">
<insert tableName="feature">
<column name="key" value="openWeatherMap"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -8,4 +8,5 @@
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<include file="1.2.5-webservices/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.7/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.22/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -15,4 +15,16 @@ abstracto.featureModes.videoDetails.mode=videoDetails
abstracto.featureModes.videoDetails.enabled=false
abstracto.featureFlags.threadReader.featureName=threadReader
abstracto.featureFlags.threadReader.enabled=false
abstracto.featureFlags.threadReader.enabled=false
abstracto.feature.webservices.openweathermap.geocodingURL=http://api.openweathermap.org/geo/1.0/direct?q=%s&limit=%s&appid=%s
abstracto.feature.webservices.openweathermap.weatherDataURL=https://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&appid=%s&units=metric&lang=%s
abstracto.feature.webservices.openweatherMap.apiKey=${OPEN_WEATHER_MAP_API_KEY}
abstracto.featureFlags.openWeatherMap.featureName=openWeatherMap
abstracto.featureFlags.openWeatherMap.enabled=false
abstracto.systemConfigs.openWeatherMapLanguageKey.name=openWeatherMapLanguageKey
abstracto.systemConfigs.openWeatherMapLanguageKey.stringValue=en

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>webservices</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,4 +14,11 @@
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -3,4 +3,5 @@ package dev.sheldan.abstracto.webservices.config;
public class WebServicesSlashCommandNames {
public static final String YOUTUBE = "youtube";
public static final String URBAN = "urban";
public static final String WEATHER = "weather";
}

View File

@@ -5,7 +5,10 @@ import lombok.Getter;
@Getter
public enum WebserviceFeatureDefinition implements FeatureDefinition {
YOUTUBE("youtube"), URBAN_DICTIONARY("urban"), THREAD_READER("threadReader");
YOUTUBE("youtube"),
URBAN_DICTIONARY("urban"),
THREAD_READER("threadReader"),
OPEN_WEATHER_MAP("openWeatherMap");
private String key;

View File

@@ -0,0 +1,25 @@
package dev.sheldan.abstracto.webservices.openweathermap.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.webservices.config.WebserviceFeatureDefinition;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class OpenWeatherMapConfig implements FeatureConfig {
public static final String OPEN_WEATHER_MAP_LANGUAGE_KEY_SYSTEM_CONFIG_KEY = "openWeatherMapLanguageKey";
@Override
public FeatureDefinition getFeature() {
return WebserviceFeatureDefinition.OPEN_WEATHER_MAP;
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(OPEN_WEATHER_MAP_LANGUAGE_KEY_SYSTEM_CONFIG_KEY);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.webservices.openweathermap.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class LocationNotFoundException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "no_weather_location_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GeoCodingLocation {
@SerializedName("name")
private String name;
@SerializedName("lat")
private Double latitude;
@SerializedName("lon")
private Double longitude;
@SerializedName("country")
private String countryKey;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class GeoCodingResult {
private List<GeoCodingLocation> results;
}

View File

@@ -0,0 +1,37 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.awt.*;
import java.time.Instant;
@Getter
@Setter
@Builder
public class WeatherResponseModel {
private Float temperature;
private Float minTemperature;
private Float maxTemperature;
private String mainWeather;
private String description;
private Float feelsLikeTemperature;
private Integer humidity;
private Integer pressure;
private Integer seaLevelPressure;
private Integer groundLevelPressure;
private Float rain1H;
private Float rain3H;
private Float snow1H;
private Float snow3H;
private Integer clouds;
private Instant sunrise;
private Instant sunset;
private Integer visibility;
private Instant dataCalculationTime;
private String locationName;
private String countryKey;
private Color embedColor;
private Long locationId;
}

View File

@@ -0,0 +1,34 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class WeatherResult {
@SerializedName("coord")
private WeatherResultCoordinates coordinates;
@SerializedName("weather")
private List<WeatherResultWeatherDescription> weathers;
@SerializedName("visibility")
private Integer visibility;
@SerializedName("main")
private WeatherResultMain mainWeather;
@SerializedName("rain")
private WeatherResultRain rainInfo;
@SerializedName("snow")
private WeatherResultSnow snowInfo;
@SerializedName("dt")
private Long dayTime;
@SerializedName("clouds")
private WeatherResultClouds cloudInfo;
@SerializedName("sys")
private WeatherResultSystem systemInfo;
@SerializedName("timezone")
private Long timezoneShift;
@SerializedName("id")
private Long locationId;
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultClouds {
private Integer all;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultCoordinates {
@SerializedName("lon")
private Float longitude;
@SerializedName("lat")
private Float latitude;
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultMain {
@SerializedName("temp")
private Float temperature;
@SerializedName("feels_like")
private Float feelsLikeTemperature;
@SerializedName("temp_min")
private Float minTemperature;
@SerializedName("temp_max")
private Float maxTemperature;
@SerializedName("pressure")
private Integer pressure;
@SerializedName("humidity")
private Integer humidity;
@SerializedName("sea_level")
private Integer seaLevelPressure;
@SerializedName("grnd_level")
private Integer groundLevelPressure;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultRain {
@SerializedName("1h")
private Float rain1H;
@SerializedName("3h")
private Float rain3H;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultSnow {
@SerializedName("1h")
private Float snow1H;
@SerializedName("3h")
private Float snow3H;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultSystem {
@SerializedName("country")
private String country;
@SerializedName("sunrise")
private Long sunrise;
@SerializedName("sunset")
private Long sunset;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultWeatherDescription {
private Long id;
private String main;
private String description;
private String icon;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.abstracto.webservices.openweathermap.model;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WeatherResultWind {
@SerializedName("speed")
private Float speed;
@SerializedName("deg")
private Integer degrees;
@SerializedName("gust")
private Float gust;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.abstracto.webservices.openweathermap.service;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingLocation;
import dev.sheldan.abstracto.webservices.openweathermap.model.GeoCodingResult;
import dev.sheldan.abstracto.webservices.openweathermap.model.WeatherResult;
import java.io.IOException;
public interface OpenWeatherMapService {
GeoCodingResult searchForLocation(String query) throws IOException;
WeatherResult retrieveWeatherForLocation(GeoCodingLocation geoCodingLocation, String languageKey) throws IOException;
}

View File

@@ -0,0 +1,7 @@
package dev.sheldan.abstracto.webservices.openweathermap.service;
import java.awt.*;
public interface WeatherService {
Color getColorForTemperature(Float temperature);
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto</groupId>
<artifactId>abstracto-application</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.abstracto.core</groupId>
<artifactId>core</artifactId>
<version>1.4.21</version>
<version>1.4.25</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>

View File

@@ -11,9 +11,7 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.CustomTemplateNotFoundException;
import dev.sheldan.abstracto.core.exception.UploadFileTooLargeException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.template.commands.GetCustomTemplateModel;
import dev.sheldan.abstracto.core.service.ChannelService;
@@ -30,8 +28,6 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -67,28 +63,10 @@ public class GetCustomTemplate extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String templateKey = (String) commandContext.getParameters().getParameters().get(0);
GetCustomTemplateModel model = getModel(templateKey, commandContext.getGuild());
File tempFile = fileService.createTempFile(templateKey + ".ftl");
try {
fileService.writeContentToFile(tempFile, model.getTemplateContent());
long maxFileSize = commandContext.getGuild().getIdLong();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_CUSTOM_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, commandContext.getGuild().getIdLong());
messageToSend.getAttachedFiles().get(0).setFile(tempFile);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInMessageChannelList(GET_CUSTOM_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
} catch (IOException e) {
throw new AbstractoRunTimeException(e);
} finally {
try {
fileService.safeDelete(tempFile);
} catch (IOException e) {
log.error("Failed to delete temporary get custom template file {}.", tempFile.getAbsoluteFile(), e);
}
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_CUSTOM_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, commandContext.getGuild().getIdLong());
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(interactionHook -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromSuccess());
}
private GetCustomTemplateModel getModel(String templateKey, Guild guild) {
@@ -109,27 +87,10 @@ public class GetCustomTemplate extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String templateKey = slashCommandParameterService.getCommandOption(TEMPLATE_KEY_PARAMETER, event, String.class);
GetCustomTemplateModel model = getModel(templateKey, event.getGuild());
File tempFile = fileService.createTempFile(templateKey + ".ftl");
try {
fileService.writeContentToFile(tempFile, model.getTemplateContent());
long maxFileSize = event.getGuild().getMaxFileSize();
// in this case, we cannot upload the file, so we need to fail
if(tempFile.length() > maxFileSize) {
throw new UploadFileTooLargeException(tempFile.length(), maxFileSize);
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_CUSTOM_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, event.getGuild().getIdLong());
messageToSend.getAttachedFiles().get(0).setFile(tempFile);
return interactionService.replyMessageToSend(messageToSend, event)
.thenApply(interactionHook -> CommandResult.fromSuccess());
} catch (IOException e) {
throw new AbstractoRunTimeException(e);
} finally {
try {
fileService.safeDelete(tempFile);
} catch (IOException e) {
log.error("Failed to delete temporary get custom template file {}.", tempFile.getAbsoluteFile(), e);
}
}
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_CUSTOM_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, event.getGuild().getIdLong());
return interactionService.replyMessageToSend(messageToSend, event)
.thenAccept(interactionHook -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(interactionHook -> CommandResult.fromSuccess());
}
@Override

View File

@@ -11,7 +11,6 @@ import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.commands.config.ConfigModuleDefinition;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.exception.TemplateNotFoundException;
import dev.sheldan.abstracto.core.interaction.InteractionService;
import dev.sheldan.abstracto.core.models.template.commands.GetTemplateModel;
@@ -27,8 +26,6 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -63,44 +60,20 @@ public class GetTemplate extends AbstractConditionableCommand {
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
String templateKey = (String) commandContext.getParameters().getParameters().get(0);
GetTemplateModel model = getModel(templateKey);
File tempFile = fileService.createTempFile(templateKey + ".ftl");
try {
fileService.writeContentToFile(tempFile, model.getTemplateContent());
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, commandContext.getGuild().getIdLong());
messageToSend.getAttachedFiles().get(0).setFile(tempFile);
return FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel()))
.thenAccept(interactionHook -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(unused -> CommandResult.fromSuccess());
} catch (IOException e) {
throw new AbstractoRunTimeException(e);
} finally {
try {
fileService.safeDelete(tempFile);
} catch (IOException e) {
log.error("Failed to delete temporary get template file {}.", tempFile.getAbsoluteFile(), e);
}
}
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
String templateKey = slashCommandParameterService.getCommandOption(TEMPLATE_KEY_PARAMETER, event, String.class);
GetTemplateModel model = getModel(templateKey);
File tempFile = fileService.createTempFile(templateKey + ".ftl");
try {
fileService.writeContentToFile(tempFile, model.getTemplateContent());
MessageToSend messageToSend = templateService.renderEmbedTemplate(GET_TEMPLATE_RESPONSE_TEMPLATE_KEY, model, event.getGuild().getIdLong());
messageToSend.getAttachedFiles().get(0).setFile(tempFile);
return interactionService.replyMessageToSend(messageToSend, event)
.thenAccept(interactionHook -> fileService.safeDeleteIgnoreException(messageToSend.getAttachedFiles().get(0).getFile()))
.thenApply(interactionHook -> CommandResult.fromSuccess());
} catch (IOException e) {
throw new AbstractoRunTimeException(e);
} finally {
try {
fileService.safeDelete(tempFile);
} catch (IOException e) {
log.error("Failed to delete temporary get template file {}.", tempFile.getAbsoluteFile(), e);
}
}
}
private GetTemplateModel getModel(String templateKey) {

Some files were not shown because too many files have changed in this diff Show More