Compare commits

...

65 Commits

Author SHA1 Message Date
release-bot
f158580897 [maven-release-plugin] prepare release sissi-1.5.16 2025-11-29 13:25:48 +00:00
Sheldan
4411995e65 [SIS-xxx] abstracto upgrade 2025-11-29 14:22:13 +01:00
Sheldan
c9b5258798 [SIS-xxx] changing the setup for donation tracking 2025-11-29 14:21:48 +01:00
Sheldan
ea5a7509bc [SIS-xxx] re-ordering title in leave message 2025-11-27 20:39:02 +01:00
Sheldan
40703005e1 [SIS-xxx] updating join and leave embeds to work with default template 2025-11-23 15:33:06 +01:00
Sheldan
fe036d7010 [SIS-xxx] removing empty spaces and formatting the id better 2025-11-13 00:57:03 +01:00
release-bot
2547d68dfc Commit from GitHub Actions (Publishes a new version of Sissi) 2025-11-12 23:27:53 +00:00
release-bot
b8b59b09c4 [maven-release-plugin] prepare for next development iteration 2025-11-12 23:20:46 +00:00
release-bot
774c142238 [maven-release-plugin] prepare release sissi-1.5.15 2025-11-12 23:20:45 +00:00
Sheldan
5b6160b0d2 [SIS-xxx] upgrading abstracto version
adding custom template for leave message
updating template to fit new leave model
2025-11-13 00:11:01 +01:00
release-bot
453b2214cd Commit from GitHub Actions (Publishes a new version of Sissi) 2025-10-12 18:42:28 +00:00
release-bot
071c603e54 [maven-release-plugin] prepare for next development iteration 2025-10-12 18:37:06 +00:00
release-bot
31538747f5 [maven-release-plugin] prepare release sissi-1.5.14 2025-10-12 18:37:05 +00:00
Sheldan
afb11dc74a [SIS-xxx] upgrading abstracto version 2025-10-12 20:19:40 +02:00
Sheldan
dd5bb63962 [SIS-xxx] fixing meetup display without location 2025-10-03 18:13:58 +02:00
release-bot
28da63db37 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-09-10 23:23:31 +00:00
release-bot
18755afb57 [maven-release-plugin] prepare for next development iteration 2025-09-10 23:16:17 +00:00
release-bot
ccfb64324e [maven-release-plugin] prepare release sissi-1.5.13 2025-09-10 23:16:15 +00:00
Sheldan
d33ce3da6c [SIS-xxx] upgrading abstracto version 2025-09-11 01:11:14 +02:00
release-bot
16553e0299 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-09-10 22:25:34 +00:00
release-bot
dd02b07ff6 [maven-release-plugin] prepare for next development iteration 2025-09-10 22:18:03 +00:00
release-bot
43e372e07a [maven-release-plugin] prepare release sissi-1.5.12 2025-09-10 22:18:01 +00:00
Sheldan
ba42a10122 [SIS-xxx] upgrading abstracto version 2025-09-11 00:12:49 +02:00
Sheldan
28d68b13c7 [SIS-xxx] adding logging to see which quote is sent in the case of random quotes 2025-08-07 19:32:44 +02:00
release-bot
ea0f1fde15 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-08-05 22:16:39 +00:00
release-bot
4904980935 [maven-release-plugin] prepare for next development iteration 2025-08-05 22:11:04 +00:00
release-bot
222e48021f [maven-release-plugin] prepare release sissi-1.5.11 2025-08-05 22:11:02 +00:00
Sheldan
c39996f117 [SIS-xxx] adding docker and helm build to build file 2025-08-06 00:03:56 +02:00
Sheldan
f90defbc64 [SIS-xxx] updating the not properly released version 2025-08-06 00:01:15 +02:00
Sheldan
48c06cf370 [SIS-xxx] fixing dependencies for docker packaging build 2025-08-06 00:00:56 +02:00
release-bot
14d90722b7 [maven-release-plugin] prepare for next development iteration 2025-08-04 19:18:51 +00:00
release-bot
6c48dd790f [maven-release-plugin] prepare release sissi-1.5.10 2025-08-04 19:18:49 +00:00
Sheldan
9e1f7f0263 [SIS-xxx] updating abstracto version to 1.6.12 2025-08-04 21:13:45 +02:00
Sheldan
15ec88c03f [SIS-xxx] fixing name of reason template 2025-07-21 17:42:25 +02:00
release-bot
aeb89533e2 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-07-21 15:33:32 +00:00
release-bot
483d6fde66 [maven-release-plugin] prepare for next development iteration 2025-07-21 15:26:57 +00:00
release-bot
f95beddffa [maven-release-plugin] prepare release sissi-1.5.9 2025-07-21 15:26:55 +00:00
Sheldan
7faafc9ad1 [SIS-xxx] adding multiline support and allowing multiple words for orange sun doge command alternative 2025-07-21 17:18:46 +02:00
Sheldan
1de0cae589 [SIS-xxx] adding self mute command 2025-07-21 16:21:17 +02:00
release-bot
dfbf15ad61 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-07-20 09:55:16 +00:00
release-bot
db271cd448 [maven-release-plugin] prepare for next development iteration 2025-07-20 09:50:00 +00:00
release-bot
8f289baadd [maven-release-plugin] prepare release sissi-1.5.8 2025-07-20 09:49:58 +00:00
Sheldan
2ee5578f4e [SIS-xxx] fixing update issue 2025-07-20 11:43:45 +02:00
Sheldan
b458d8d3a8 [SIS-xxx] adding starboard customization to change color based on disk (semi hardcoded)
upgrading abstracto version
adding avatar to quote response
2025-07-20 11:33:49 +02:00
release-bot
f8353ca19b Commit from GitHub Actions (Publishes a new version of Sissi) 2025-07-13 20:18:05 +00:00
release-bot
023b22b2c2 [maven-release-plugin] prepare for next development iteration 2025-07-13 20:13:09 +00:00
release-bot
9ada2c39a2 [maven-release-plugin] prepare release sissi-1.5.7 2025-07-13 20:13:08 +00:00
Sheldan
d61d378a6a [SIS-xxx] upgrading abstracto to get change for embed cleanup job 2025-07-13 22:09:41 +02:00
release-bot
09af4b516f Commit from GitHub Actions (Publishes a new version of Sissi) 2025-07-13 18:18:03 +00:00
release-bot
0e50a7b826 [maven-release-plugin] prepare for next development iteration 2025-07-13 18:13:04 +00:00
release-bot
d4dc118f66 [maven-release-plugin] prepare release sissi-1.5.6 2025-07-13 18:13:02 +00:00
Sheldan
be35a84dcf [SIS-xxx] upgrading to abstracto version supporting components v2
changing meetup and quote command responses to use components v2
changing column name of quote attachment
2025-07-13 20:09:45 +02:00
release-bot
5f6df14c24 [maven-release-plugin] prepare for next development iteration 2025-05-29 20:30:01 +00:00
release-bot
a60cf5d745 [maven-release-plugin] prepare release sissi-1.5.5 2025-05-29 20:30:00 +00:00
Sheldan
6e7ae8b36f [SIS-xxx] upgrading abstracto version and preparing to release 2025-05-29 22:26:07 +02:00
release-bot
3f22955df2 Commit from GitHub Actions (Publishes a new version of Sissi) 2025-04-01 12:12:50 +00:00
release-bot
938f9e5cd4 [maven-release-plugin] prepare for next development iteration 2025-04-01 12:07:29 +00:00
release-bot
45516f2458 [maven-release-plugin] prepare release sissi-1.5.4 2025-04-01 12:07:28 +00:00
Sheldan
9d9c8beb88 [SIS-xxx] upgrading abstracto version 2025-04-01 14:04:17 +02:00
Sheldan
427250e0bc [SIS-xxx] adding ability to select multiple decisions for meetup notify
making meetup notify slash command only
2025-03-31 22:32:04 +02:00
Sheldan
35d20be13b [SIS-xxx] updating privacy policy 2025-02-23 23:18:18 +01:00
Sheldan
693bc2cd2c [SIS-xxx] creating privacy policy 2025-02-23 23:08:24 +01:00
release-bot
c62757590a Commit from GitHub Actions (Publishes a new version of Sissi) 2025-02-23 20:37:20 +00:00
release-bot
1b2d326655 [maven-release-plugin] prepare for next development iteration 2025-02-23 20:32:54 +00:00
release-bot
180c2681e0 [maven-release-plugin] prepare release sissi-1.5.3 2025-02-23 20:32:53 +00:00
143 changed files with 1284 additions and 554 deletions

4
.env
View File

@@ -1,4 +1,4 @@
REGISTRY_PREFIX=harbor.sheldan.dev/sissi/
ABSTRACTO_PREFIX=harbor.sheldan.dev/abstracto/
VERSION=1.5.2
ABSTRACTO_VERSION=1.6.4
VERSION=1.5.15
ABSTRACTO_VERSION=1.6.17

View File

@@ -31,3 +31,23 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
user: Sheldan
token: ${{ secrets.ABSTRACTO_PAT }}
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.sheldan.dev
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_TOKEN }}
- name: Load env file
id: dotenv
uses: falti/dotenv-action@v1.0.4
with:
path: .env
- name: Docker build
run: docker compose build
env:
REGISTRY_PREFIX: ${{ steps.dotenv.outputs.registry_prefix }}
VERSION: ${{ steps.dotenv.outputs.version }}
- name: Helm package and push
working-directory: ./deployment/helm/
run: |-
helm package sissi

164
PRIVACY_POLICY.md Normal file
View File

@@ -0,0 +1,164 @@
# Sissi Discord bot privacy policy
Last updated: 23.02.2025
## Description
The bot requires some information to function properly and in a reasonable way. These features heavily depend which features are enabled for which server, and this list just presents ALL of the features available.
The detailed list of what information is stored and processed is the following:
### General
* your Discord user ID (in combination with the server ID) is used to uniquely identify you and associate various properties, such as experience, level, opened modmail threads etc
* the IDs of the servers this bot is in
* the IDs of the channel in the servers this bot is in
* the names of channel groups which were given by their creator
* the server aliases which were created for commands
* the name of emotes which are used in the bot for convenience, if they are customized
* towards which channel (identified by ID) certain messages by the bot are posted. e.g. logging, news, starboard
* **no message content, username, channel name or role name is stored, except at the places where its mentioned**
* most of the stored records have a 'created' and 'updated' timestamp, in order to assist in examining bugs and malfunctions
* which commands have which cooldown in which channel group and in which channel group they are disabled
* which channel is in which channel group
* which role is allowed to execute which command
* which features are enabled
* which feature modes are enabled
* **aliases** created for the commands
* **information** necessary to handle components (buttons, select menus). This information is of varying nature and can be user identifiable information
* **emotes** which should be used for varying places (assignable roles, particular emotes which are overwriten, such as star board)
### Moderation
* **mute reason**, duration, mute date, who muted whom and in which message was the mute executed
* the names of filtered invite link servers in order to find out if it would be valid to allow the invite
* any configured allowed invite links the server ID and the actually used invite
* this is necessary in order to determine the server via its ID and allow other unknown invite links. The invite link is necessary as there is no way to map server ID to actual server
* configured profanity regexes
* reported profanities, including which message contains the profanity, and the message which was used to report the profanity, and whether it was identified as a true profanity
* **the text of notes regarding users**
* this is used to enable taking notes about users, and the content is stored directly
* meta information regarding warnings
* **reason for the warning**
* date of the warning
* the user who warned a user
* whether the warning was decayed and when
* the infractions of each user accompanied with **reason** (if available): warns, mutes, bans, kicks
### Giveaway
* the give away information: **description**, provider, manager, target date, winners and participants
### Embedded messages
* embedded message information
* this information includes who embedded which message in which channel and is deleted after a few days
### Emote usage tracking
* the name of emotes which are being tracked in the emote usage tracking system for purely convenience reasons
* **who** used which emote is **not** tracked
* at which day an emote was used how many times
* whether the emote was a reaction
### Reminder
* **the message content** in order to provide you with the reminder text
* the date it was created, and the date it is due
* the id of the message which contained the command
* whether you have been reminded
* the users who have joined the reminders
### Modmail
* the information that a modmail thread existed (creation, status and close date), and the IDs of the messages that have been sent in both directions
### News
* the ORF integration stores which posts from ORF RSS were already sent.
* which ORF RSS feeds were subscribed
### Polls
* the **text** of polls, the creator of polls and who made which decision in polls
### Starboard (best-of)
* the message which was the origin for the starboard post
* the message which was the resulting starboard post
* the author of the message and the amount of stars
* who reacted to a starboard post
* this is necessary to provide the information about 'top star giver' and to disallow duplicate starboard reactions
### Suggestion
* **the message content** of the message used to create the suggestion
* this was used for the message used to update the status of a suggestion, but this is currently disabled
* the author of the suggestion and the message which has been posted in the suggestions channel
* every suggestion will be deleted completely from the database a few days after it has reached a final state (rejected, denied, accepted)
* whether you voted for a suggestion and which decision you made
### Leveling system
* the amount of messages which were considered for the leveling system
* it only considers a message once per minute, so it does not directly translate to your absolute message count
* the amount of experience, and the experience level you have
* whether experience gain has been disabled for you
* the role you received because of the experience system
* which roles are configured to be used as experience roles and at which level they are assigned
* which roles are used to disable experience gain
### Entertainment
* PressF: for **what** the pressF was initiated by whom and who participated
### Economy
* The amount of credits for each user
### Assignable roles
* the names of assignable role places and assignable role button text, together with the associated emote markdown (if given)
* the assigned assignable roles for each member in order to provide the 'unique' assignable role functionality
* custom configured conditions to enable a level restriction
### Weekly items (for Miepscord)
* The text of the weekly art posts and the creator of them
### Sticky roles
* The roles a user had when leaving the server to be re-applied when re-joining (opt out possible)
### Meetups
* The meta data of meetups: text, location, creator and the decision of each user interacting with the meetup. This information will be deleted a few days after a meetup has passed/is cancelled.
### Twitch
* The ID/**name** of streamers to follow accompanied with their discord ID
* The start/end dates of their streams
* The individual sections of streams identified by title and game for updating the message
### Custom commands
* the **names** given to custom commands and the configured **response text**
## Grafana dashboard
There is also a [Grafana](https://grafana.com/) dashboard in order to inspect how the bot is operating.
The information visible in this dashboard is:
* message events
* Discord gateway ping
* starboard reactions
* amount of command executions
* emotes currently being processed for tracking
* embedded messages
* invite filter activity
* amount of experience which is currently being processed
All of this information cannot be linked to any user (or any server for that matter, if the bot would be in multiple servers) and is deleted after 15 days.
## How can I decide which information is collected?
It is not possible to opt-out of singular sub-services of the bot. Should you decide that your information should not be collected, please cease usage of the bot immediately (leave any guild the bot operates in).
_Should you decide to no longer utilize the bot, you may request your data to be erased within 30 days as per GDPR if you are a citizen of the EU. You can do this by sending a message to the user "sheldan" on Discord: GDPR Data removal <Username> <UserId>. If your request is incomplete, we cannot acknowledge it and therefore your data will not be removed. In order to identify authentic requests, please contact modmail beforehand by sending a direct message to the bot and stating your intention._
## Open source content
This bot uses the following open source libraries and frameworks:
* [abstracto](https://github.com/Sheldan/abstracto) is used as a base for this bot, providing most of the functionalities
* [JDA](https://github.com/DV8FromTheWorld/JDA/) The Discord API Wrapper used
* [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 [k3s](https://k3s.io/) to orchestrate the containers
* [Liquibase](https://github.com/liquibase/liquibase) is used to manage changes to the database
* [Prometheus](https://prometheus.io) to scrap and collect the metrics about how the bot is operating
* [Grafana](https://grafana.com) to visualize metrics of the bot and [Loki](https://grafana.com/oss/loki/) for logging

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>executable</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -34,6 +34,11 @@
<artifactId>rssreader</artifactId>
<version>${rssreader.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.sissi.application.module.custom</groupId>
<artifactId>sissi-customizations</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<artifactId>image-generation-custom</artifactId>

View File

@@ -50,16 +50,13 @@ public class OrangeSunDogeCommandAlternative implements CommandAlternative {
@Override
public boolean shouldExecute(UnParsedCommandParameter parameter, Guild guild, Message message) {
String contentStripped = message.getContentRaw();
String[] parameters = contentStripped.split(" ");
return parameters.length == 1 && featureFlagService.isFeatureEnabled(imageGenerationFeatureConfig, guild.getIdLong());
return featureFlagService.isFeatureEnabled(imageGenerationFeatureConfig, guild.getIdLong());
}
@Override
public void execute(UnParsedCommandParameter parameter, Message message) {
String contentStripped = message.getContentRaw();
List<String> parameters = Arrays.asList(contentStripped.split(" "));
String inputText = commandRegistry.getCommandName(parameters.get(0), message.getGuild().getIdLong());
String inputText = commandRegistry.getCommandName(contentStripped, message.getGuild().getIdLong());
File triggeredGifFile = imageGenerationService.getOrangeSunDogeImage(inputText);
MessageToSend messageToSend = templateService.renderEmbedTemplate(DOGE_ORANGE_SUN_RESPONSE_TEMPLATE_KEY, new Object(), message.getGuildIdLong());
// template support does not support binary files

View File

@@ -8,6 +8,8 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component
public class ImageGenerationService {
@@ -20,7 +22,7 @@ public class ImageGenerationService {
public File getOrangeSunDogeImage(String inputText) {
try {
return httpService.downloadFileToTempFile(dogeOrangeSunUrl.replace("{1}", inputText));
return httpService.downloadFileToTempFile(dogeOrangeSunUrl.replace("{1}", URLEncoder.encode(inputText, StandardCharsets.UTF_8)));
} catch (IOException e) {
throw new AbstractoRunTimeException(String.format("Failed to download orange doge image for url %s with error %s", inputText, e.getMessage()));
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application.module.custom</groupId>
<artifactId>sissi-customizations</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,68 @@
package dev.sheldan.sissi.module.custom.moderation.commands;
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.sissi.module.custom.moderation.config.ModerationCustomFeatureDefinition;
import dev.sheldan.sissi.module.custom.moderation.service.SelfMuteServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
public class SelfMute extends AbstractConditionableCommand {
public static final String DURATION_PARAMETER = "duration";
@Autowired
private SelfMuteServiceBean selfMuteServiceBean;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Duration muteDuration = (Duration) parameters.get(0);
return selfMuteServiceBean.selfMuteMember(commandContext.getAuthor(), muteDuration)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter durationParameter = Parameter
.builder()
.name(DURATION_PARAMETER)
.type(Duration.class)
.templated(true)
.build();
List<Parameter> parameters = Arrays.asList(durationParameter);
HelpInfo helpInfo = HelpInfo
.builder()
.templated(true)
.build();
return CommandConfiguration.builder()
.name("selfMute")
.async(true)
.module(UtilityModuleDefinition.UTILITY)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)
.help(helpInfo)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
}

View File

@@ -0,0 +1,30 @@
package dev.sheldan.sissi.module.custom.moderation.service;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Component
public class SelfMuteServiceBean {
@Autowired
private MemberService memberService;
@Autowired
private TemplateService templateService;
private static final String SELF_MUTE_REASON_TEMPLATE = "self_mute_reason";
public CompletableFuture<Void> selfMuteMember(Member member, Duration duration) {
String reason = templateService.renderSimpleTemplate(SELF_MUTE_REASON_TEMPLATE, member.getGuild().getIdLong());
log.info("Self muting user {} in server {}.", member.getIdLong(), member.getGuild().getIdLong());
return memberService.timeoutUser(member, duration, reason);
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<include file="seedData/data.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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<property name="moderationCustomFeature" value="(SELECT id FROM feature WHERE key = 'moderationCustom')"/>
<property name="utilityModule" value="(SELECT id FROM module WHERE name = 'utility')"/>
<changeSet author="Sheldan" id="moderationCustom_selfmute-commands">
<insert tableName="command">
<column name="name" value="selfMute"/>
<column name="module_id" valueComputed="${utilityModule}"/>
<column name="feature_id" valueComputed="${moderationCustomFeature}"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -5,4 +5,5 @@
<include file="1.1.0/collection.xml" relativeToChangelogFile="true"/>
<include file="1.2.1/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.45/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.9/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>application</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,6 +15,11 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.scheduling</groupId>
<artifactId>scheduling-int</artifactId>
<version>${abstracto.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -28,6 +33,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -9,8 +9,6 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME;
@RestController
@RequestMapping(value = "/debra")
public class DebraDonationStatusController {
@@ -20,50 +18,38 @@ public class DebraDonationStatusController {
@GetMapping(value = "/latestDonations", produces = "application/json")
public DonationStats getLatestDonations() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount(serverId);
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount();
List<DonationInfo> donations = donationService.getLatestDonations(donationResponse, Integer.MAX_VALUE)
.stream()
.map(DonationInfo::fromDonationItemModel)
.toList();
return DonationStats
.builder()
.totalAmount(donationResponse.getPage().getCollected())
.totalAmount(donationResponse.getCurrentDonationAmount())
.donations(donations)
.build();
}
@GetMapping(value = "/highestDonations", produces = "application/json")
public DonationStats getHighestDonations() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount(serverId);
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount();
List<DonationInfo> donations = donationService.getHighestDonations(donationResponse, Integer.MAX_VALUE)
.stream()
.map(DonationInfo::fromDonationItemModel)
.toList();
return DonationStats
.builder()
.totalAmount(donationResponse.getPage().getCollected())
.totalAmount(donationResponse.getCurrentDonationAmount())
.donations(donations)
.build();
}
@GetMapping(value = "/campaignInfo", produces = "application/json")
public CampaignInfo getCampaignInfo() {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount(serverId);
DonationsResponse donationResponse = donationService.getSynchronizedCachedDonationAmount();
Description pageObject = donationResponse.getPage();
return CampaignInfo
.builder()
.collected(pageObject.getCollected())
.collectedNet(pageObject.getCollectedNet())
.donationCount(donationResponse.getDonationCount())
.currency(pageObject.getCurrency())
.percent(pageObject.getPercent())
.displayName(pageObject.getDisplayName())
.slug(pageObject.getSlug())
.target(pageObject.getTarget())
.build();
}
}

View File

@@ -36,8 +36,8 @@ public class EndlessStreamController {
public EndlessStreamInfo getLatestDonations(@PathVariable("id") Long id) {
Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
EndlessStream endlessStream = endlessStreamManagementServiceBean.getEndlessStream(id);
DonationsResponse donationInfo = donationService.getSynchronizedCachedDonationAmount(serverId);
BigDecimal collectedAmount = donationInfo.getPage().getCollected();
DonationsResponse donationInfo = donationService.getSynchronizedCachedDonationAmount();
BigDecimal collectedAmount = donationInfo.getCurrentDonationAmount();
Long minuteRate = configService.getLongValueOrConfigDefault(DebraFeatureConfig.ENDLESS_STREAM_MINUTE_RATE, serverId);
Instant endDate = endlessStream.getStartTime().plus(collectedAmount.multiply(new BigDecimal(minuteRate)).toBigInteger().longValue(), ChronoUnit.MINUTES);
return EndlessStreamInfo

View File

@@ -6,9 +6,9 @@ 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.CommandParameterKey;
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;
@@ -26,7 +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.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -70,11 +69,12 @@ public class Donations extends AbstractConditionableCommand {
Integer selectionValue = (Integer) parameters.get(1);
Integer top = null;
Integer latest = null;
switch (type) {
case "top": top = selectionValue; break;
default:
case "latest" :
latest = selectionValue; break;
if(type != null) {
DonationsTypeParameterKey typeKey = CommandParameterKey.getEnumFromKey(DonationsTypeParameterKey.class, type);
switch (typeKey) {
case LATEST -> latest = selectionValue;
case TOP -> top = selectionValue;
}
}
messageToSend = getDonationMessageToSend(commandContext.getGuild().getIdLong(), top, latest);
}
@@ -98,11 +98,10 @@ public class Donations extends AbstractConditionableCommand {
Integer top = null;
Integer latest = null;
if(selectionType != null) {
switch (selectionType) {
case "top": top = selectionValue; break;
default:
case "latest" :
latest = selectionValue; break;
DonationsTypeParameterKey typeKey = CommandParameterKey.getEnumFromKey(DonationsTypeParameterKey.class, selectionType);
switch (typeKey) {
case LATEST -> latest = selectionValue;
case TOP -> top = selectionValue;
}
}
@@ -113,7 +112,7 @@ public class Donations extends AbstractConditionableCommand {
private MessageToSend getDonationMessageToSend(Long serverId, Integer top, Integer latest) {
DonationsModel donationModel;
DonationsResponse donationResponse = donationService.fetchCurrentDonationAmount(serverId);
DonationsResponse donationResponse = donationService.fetchCurrentDonations();
donationModel = donationConverter.convertDonationResponse(donationResponse);
if(top != null) {
donationModel.setDonations(donationService.getHighestDonations(donationResponse, top));
@@ -146,7 +145,7 @@ public class Donations extends AbstractConditionableCommand {
.templated(true)
.name(SELECTION_PARAMETER)
.optional(true)
.type(String.class)
.type(DonationsTypeParameterKey.class)
.build();

View File

@@ -0,0 +1,13 @@
package dev.sheldan.sissi.module.debra.commands;
import dev.sheldan.abstracto.core.command.execution.CommandParameterKey;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum DonationsTypeParameterKey implements CommandParameterKey {
TOP("top"), LATEST("latest");
private String key;
}

View File

@@ -13,7 +13,6 @@ public class DebraFeatureConfig implements FeatureConfig {
public static final String DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY = "debraDonationNotificationDelayMillis";
public static final String ENDLESS_STREAM_MINUTE_RATE = "endlessStreamMinuteRate";
public static final String DEBRA_DONATION_API_FETCH_SIZE_KEY = "debraDonationApiFetchSize";
public static final String DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME = "DEBRA_DONATION_NOTIFICATION_SERVER_ID";
@Override
public FeatureDefinition getFeature() {
@@ -27,6 +26,6 @@ public class DebraFeatureConfig implements FeatureConfig {
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY, DEBRA_DONATION_API_FETCH_SIZE_KEY, ENDLESS_STREAM_MINUTE_RATE);
return Arrays.asList(DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY, ENDLESS_STREAM_MINUTE_RATE);
}
}

View File

@@ -10,6 +10,5 @@ import org.springframework.context.annotation.Configuration;
@Setter
@ConfigurationProperties(prefix = "sissi.debra")
public class DebraProperties {
private String websocketURL;
private String donationAPIUrl;
private String donationPageUrl;
}

View File

@@ -1,6 +1,6 @@
package dev.sheldan.sissi.module.debra.converter;
import dev.sheldan.sissi.module.debra.model.api.Donation;
import dev.sheldan.sissi.module.debra.model.api.DonationDto;
import dev.sheldan.sissi.module.debra.model.api.DonationsResponse;
import dev.sheldan.sissi.module.debra.model.commands.DonationItemModel;
import dev.sheldan.sissi.module.debra.model.commands.DonationsModel;
@@ -9,20 +9,19 @@ import org.springframework.stereotype.Component;
@Component
public class DonationConverter {
public DonationItemModel convertDonation(Donation donation) {
public DonationItemModel convertDonation(DonationDto donation) {
return DonationItemModel
.builder()
.donationAmount(donation.getAmount())
.firstName(donation.getFirstname())
.lastName(donation.getLastname())
.anonymous(BooleanUtils.toBoolean(donation.getAnonym()))
.name(donation.getName())
.anonymous(BooleanUtils.toBoolean(donation.getAnonymous()))
.build();
}
public DonationsModel convertDonationResponse(DonationsResponse response) {
return DonationsModel
.builder()
.totalAmount(response.getPage().getCollected())
.totalAmount(response.getCurrentDonationAmount())
.build();
}
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.sissi.module.debra.job;
import dev.sheldan.sissi.module.debra.service.DonationService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
@Slf4j
@DisallowConcurrentExecution
@Component
@PersistJobDataAfterExecution
public class DonationFetchJob extends QuartzJobBean {
@Autowired
private DonationService donationService;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
log.info("Checking for new donations.");
donationService.checkForNewDonations();
} catch (Exception e) {
log.error("Failed to check for new donations.", e);
}
}
}

View File

@@ -1,96 +0,0 @@
package dev.sheldan.sissi.module.debra.listener;
import dev.sheldan.abstracto.core.listener.AsyncStartupListener;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.sissi.module.debra.config.DebraProperties;
import dev.sheldan.sissi.module.debra.model.listener.DonationResponseModel;
import dev.sheldan.sissi.module.debra.service.DonationService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME;
@Component
@Slf4j
public class WebsocketListener extends WebSocketListener implements AsyncStartupListener {
@Autowired
private DonationService donationService;
@Autowired
private DebraProperties debraProperties;
@Autowired
private ConfigService configService;
private WebSocket webSocketObj;
private OkHttpClient clientObj;
@Override
public void onOpen(WebSocket webSocket, Response response) {
log.info("Connected to donation websocket.");
super.onOpen(webSocket, response);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
CompletableFuture.runAsync(() -> {
log.info("Handling received message on websocket.");
try {
Long targetServerId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
Long delayMillis = configService.getLongValueOrConfigDefault(DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY, targetServerId);
log.info("Waiting {} milli seconds to send notification.", delayMillis);
Thread.sleep(delayMillis);
log.info("Loading new donation amount and sending notification.");
DonationResponseModel donation = donationService.parseDonationFromMessage(text);
donationService.sendDonationNotification(donation).thenAccept(unused -> {
log.info("Successfully notified about donation.");
}).exceptionally(throwable -> {
log.error("Failed to notify about donation.", throwable);
return null;
});
} catch (Exception exception) {
log.error("Failed to handle websocket message.", exception);
}
});
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
log.warn("Websocket connection failed...", t);
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
log.info("Closing websocket connection. It was closed with code {} and reason {}.", code, reason);
}
@Override
public void execute() {
if(clientObj != null) {
clientObj.connectionPool().evictAll();
clientObj.dispatcher().executorService().shutdownNow();
}
clientObj = new OkHttpClient.Builder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true)
.build();
startConnection(clientObj);
clientObj.dispatcher().executorService().shutdown();
}
private void startConnection(OkHttpClient client) {
log.info("Starting websocket connection.");
Request request = new Request.Builder()
.url(debraProperties.getWebsocketURL())
.build();
this.webSocketObj = client.newWebSocket(request, this);
}
}

View File

@@ -1,28 +0,0 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
@Builder
public class Description {
@SerializedName("collected")
private BigDecimal collected;
@SerializedName("target")
private BigDecimal target;
@SerializedName("currency")
private String currency;
@SerializedName("slug")
private String slug;
@SerializedName("displayname")
private String displayName;
@SerializedName("collectednet")
private BigDecimal collectedNet;
@SerializedName("percent")
private BigDecimal percent;
}

View File

@@ -1,26 +0,0 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
@Getter
@Setter
@Builder
public class Donation {
@SerializedName("amount")
private BigDecimal amount;
@SerializedName("currency")
private String currency;
@SerializedName("text")
private String text;
@SerializedName("anonym")
private Integer anonym;
@SerializedName("firstname")
private String firstname;
@SerializedName("lastname")
private String lastname;
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.sissi.module.debra.model.api;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Getter
@Setter
@Builder
@ToString
public class DonationDto {
private BigDecimal amount;
private String currency;
private String text;
private Boolean anonymous;
private String name;
private LocalDate date;
public String stringRepresentation() {
return String.format("%s %s %s %s %s", name, amount, text, anonymous, date.format(DateTimeFormatter.ISO_DATE));
}
}

View File

@@ -11,7 +11,7 @@ import java.math.BigDecimal;
@Setter
@Builder
public class DonationInfo {
private String firstName;
private String name;
private BigDecimal donationAmount;
private Boolean anonymous;
@@ -20,7 +20,7 @@ public class DonationInfo {
.builder()
.donationAmount(donationItemModel.getDonationAmount())
.anonymous(donationItemModel.getAnonymous())
.firstName(donationItemModel.getFirstName())
.name(donationItemModel.getName())
.build();
}
}

View File

@@ -1,21 +1,18 @@
package dev.sheldan.sissi.module.debra.model.api;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.math.BigInteger;
import java.math.BigDecimal;
import java.util.List;
@Getter
@Setter
@Builder
public class DonationsResponse {
@SerializedName("page")
private Description page;
@SerializedName("donation_count")
private BigInteger donationCount;
@SerializedName("donations")
private List<Donation> donations;
private BigDecimal currentDonationAmount;
private BigDecimal donationAmountGoal;
private int donationCount;
private List<DonationDto> donations;
}

View File

@@ -5,13 +5,14 @@ import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
import java.time.LocalDate;
@Getter
@Setter
@Builder
public class DonationItemModel {
private String firstName;
private String lastName;
private String name;
private LocalDate date;
private BigDecimal donationAmount;
private Boolean anonymous;
}

View File

@@ -0,0 +1,30 @@
package dev.sheldan.sissi.module.debra.model.database;
import jakarta.persistence.*;
import lombok.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "donation")
@Getter
@Setter
@EqualsAndHashCode
public class Donation {
@Id
@Column(name = "id", nullable = false)
private String id;
// we cant be sure about duplicates
@Column(name = "count")
private Integer count;
@Column(name = "created")
private Instant created;
@Column(name = "updated")
private Instant updated;
}

View File

@@ -11,6 +11,7 @@ import java.math.BigDecimal;
@ToString
public class DonationResponseModel {
private String donatorName;
private Boolean anonymous;
private BigDecimal amount;
private String message;
}

View File

@@ -0,0 +1,9 @@
package dev.sheldan.sissi.module.debra.repository;
import dev.sheldan.sissi.module.debra.model.database.Donation;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DonationRepository extends JpaRepository<Donation, String> {
}

View File

@@ -1,52 +1,52 @@
package dev.sheldan.sissi.module.debra.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dev.sheldan.abstracto.core.exception.AbstractoRunTimeException;
import dev.sheldan.abstracto.core.interaction.ComponentPayloadService;
import dev.sheldan.abstracto.core.interaction.ComponentService;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.abstracto.core.service.HashService;
import dev.sheldan.abstracto.core.service.PostTargetService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
import dev.sheldan.abstracto.core.templating.service.TemplateService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.sissi.module.debra.exception.DonationAmountNotFoundException;
import dev.sheldan.sissi.module.debra.config.DebraPostTarget;
import dev.sheldan.sissi.module.debra.config.DebraProperties;
import dev.sheldan.sissi.module.debra.converter.DonationConverter;
import dev.sheldan.sissi.module.debra.model.api.Donation;
import dev.sheldan.sissi.module.debra.model.api.DonationDto;
import dev.sheldan.sissi.module.debra.model.api.DonationsResponse;
import dev.sheldan.sissi.module.debra.model.commands.DebraInfoButtonPayload;
import dev.sheldan.sissi.module.debra.model.commands.DebraInfoModel;
import dev.sheldan.sissi.module.debra.model.commands.DonationItemModel;
import dev.sheldan.sissi.module.debra.model.commands.DonationsModel;
import dev.sheldan.sissi.module.debra.model.database.Donation;
import dev.sheldan.sissi.module.debra.model.listener.DonationResponseModel;
import dev.sheldan.sissi.module.debra.model.listener.DonationNotificationModel;
import dev.sheldan.sissi.module.debra.service.management.DonationManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.tuple.Pair;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_API_FETCH_SIZE_KEY;
import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME;
@Component
@@ -62,15 +62,9 @@ public class DonationService {
@Autowired
private TemplateService templateService;
@Autowired
private OkHttpClient okHttpClient;
@Autowired
private DonationConverter donationConverter;
@Autowired
private ConfigService configService;
@Autowired
private ChannelService channelService;
@@ -83,100 +77,209 @@ public class DonationService {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private DonationManagementServiceBean donationManagementServiceBean;
@Autowired
private HashService hashService;
@Autowired
private DonationService self;
private static final String DEBRA_DONATION_NOTIFICATION_TEMPLATE_KEY = "debra_donation_notification";
private static final Pattern MESSAGE_PATTERN = Pattern.compile("(.*) hat (\\d{1,9},\\d{2}) Euro gespendet!<br \\/>Vielen Dank!<br \\/>Nachricht:<br \\/>(.*)");
private static final String DEBRA_INFO_BUTTON_MESSAGE_TEMPLATE_KEY = "debraInfoButton";
public static final String DEBRA_INFO_BUTTON_ORIGIN = "DEBRA_INFO_BUTTON";
public DonationResponseModel parseDonationFromMessage(String message) {
Matcher matcher = MESSAGE_PATTERN.matcher(message);
if (matcher.find()) {
String donatorName = matcher.group(1);
String amountString = matcher.group(2);
BigDecimal amount = new BigDecimal(amountString.replace(',', '.'));
String donationMessage = Optional.ofNullable(matcher.group(3)).map(msg -> msg.replaceAll("(<br>)+", " ")).map(String::trim).orElse("");
return DonationResponseModel
.builder()
.message(donationMessage)
.donatorName(donatorName)
.amount(amount)
.build();
} else {
throw new IllegalArgumentException("String in wrong format");
}
}
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("d.M.y");
public List<DonationItemModel> getHighestDonations(DonationsResponse response, Integer maxCount) {
List<Donation> topDonations = response
return response
.getDonations()
.stream()
.sorted(Comparator.comparing(Donation::getAmount)
.sorted(Comparator.comparing(DonationDto::getAmount)
.reversed())
.collect(Collectors.toList());
return topDonations
.stream()
.limit(maxCount)
.map(donation -> donationConverter.convertDonation(donation))
.collect(Collectors.toList());
.toList();
}
public List<DonationItemModel> getLatestDonations(DonationsResponse response, Integer maxCount) {
return response
.getDonations()
.stream()
.sorted(Comparator.comparing(DonationDto::getDate).reversed())
.limit(maxCount)
.map(donation -> donationConverter.convertDonation(donation))
.collect(Collectors.toList());
}
public synchronized DonationsResponse getSynchronizedCachedDonationAmount(Long serverId) {
return self.getCachedDonationAmount(serverId);
public synchronized DonationsResponse getSynchronizedCachedDonationAmount() {
return self.getCachedDonationAmount();
}
@Cacheable(value = "donation-cache")
public synchronized DonationsResponse getCachedDonationAmount(Long serverId) {
return self.fetchCurrentDonationAmount(serverId);
public synchronized DonationsResponse getCachedDonationAmount() {
return self.fetchCurrentDonations();
}
public DonationsResponse fetchCurrentDonationAmount(Long serverId) {
public DonationsResponse fetchCurrentDonations() {
try {
Long fetchSize = configService.getLongValueOrConfigDefault(DEBRA_DONATION_API_FETCH_SIZE_KEY, serverId);
Request request = new Request.Builder()
.url(String.format(debraProperties.getDonationAPIUrl(), fetchSize))
.get()
.build();
Response response = okHttpClient.newCall(request).execute();
if(!response.isSuccessful()) {
log.error("Failed to retrieve donation response. Response had code {} with body {} and headers {}.",
response.code(), response.body().string(), response.headers());
throw new DonationAmountNotFoundException();
Document donationPage = Jsoup.connect(debraProperties.getDonationPageUrl()).get();
DecimalFormat decimalFormat = getDecimalFormat();
Element endValueElement = donationPage.getElementById("end-value");
String endValueString = endValueElement.text();
Elements currentValueElement = donationPage.getElementsByClass("current_amount").get(0).getElementsByClass("value");
String[] valueArray = currentValueElement.text().split(" ");
String currentValueString = valueArray[0];
String currency = valueArray[1];
BigDecimal currentValue = (BigDecimal) decimalFormat.parse(currentValueString);
BigDecimal endValue = (BigDecimal) decimalFormat.parse(endValueString);
Element list = donationPage.getElementsByClass("donor-list").first();
Elements donationElements = list.getElementsByClass("list-item");
List<DonationDto> donations = new ArrayList<>();
for (Element donationMainElement : donationElements.asList()) {
Elements nameElement = donationMainElement.getElementsByClass("donor-list-name");
Elements dateElement = donationMainElement.getElementsByClass("donor-list-date");
Elements amountElement = donationMainElement.getElementsByClass("donor-list-amount");
Elements textElement = donationMainElement.getElementsByClass("donor-list-amount-text");
LocalDate dateValue;
if (dateElement.hasText()) {
dateValue = LocalDate.parse(dateElement.text(), DATE_FORMAT);
} else {
dateValue = null;
}
BigDecimal amount;
if (amountElement.hasText()) {
String amountText = amountElement.text().split(" ")[0];
amount = (BigDecimal) decimalFormat.parse(amountText);
} else {
amount = null;
}
String additionalText = textElement.text();
String name = nameElement.text();
boolean anonymous = name.isBlank();
donations.add(DonationDto
.builder()
.anonymous(anonymous)
.name(nameElement.text())
.amount(amount)
.currency(currency)
.name(name)
.text(additionalText)
.date(dateValue)
.build());
}
Gson gson = getGson();
return gson.fromJson(response.body().string(), DonationsResponse.class);
return DonationsResponse
.builder()
.donations(donations)
.currentDonationAmount(currentValue)
.donationAmountGoal(endValue)
.donationCount(donations.size())
.build();
} catch (Exception exception) {
throw new AbstractoRunTimeException(exception);
}
}
private Gson getGson() {
return new GsonBuilder()
.registerTypeAdapter(BigDecimal.class, new BigDecimalGsonAdapter())
.create();
private DecimalFormat getDecimalFormat() {
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setGroupingSeparator('.');
symbols.setDecimalSeparator(',');
String pattern = "#,##0.0#";
DecimalFormat decimalFormat = new DecimalFormat(pattern, symbols);
decimalFormat.setParseBigDecimal(true);
return decimalFormat;
}
private DonationsModel getDonationInfoModel(Long serverId) {
return donationConverter.convertDonationResponse(fetchCurrentDonationAmount(serverId));
private DonationsModel getDonationInfoModel() {
return donationConverter.convertDonationResponse(fetchCurrentDonations());
}
public CompletableFuture<Void> sendDonationNotification(DonationResponseModel donation) throws IOException {
private String hashDonation(DonationDto donation) {
return hashService.sha256HashString(donation.stringRepresentation());
}
@Transactional
public void checkForNewDonations() {
List<Donation> allDonations = donationManagementServiceBean.getAllDonations();
Map<String, Integer> existingHashes = allDonations
.stream()
.collect(Collectors.toMap(Donation::getId, Donation::getCount));
DonationsResponse donationResponse = fetchCurrentDonations();
Map<String, Pair<Integer, DonationDto>> donationFromPageHashes = new HashMap<>();
donationResponse.getDonations().forEach(donationDto -> {
String thisHash = hashDonation(donationDto);
if(donationFromPageHashes.containsKey(thisHash)) {
donationFromPageHashes.put(thisHash, Pair.of(donationFromPageHashes.get(thisHash).getLeft() + 1, donationDto));
} else {
donationFromPageHashes.put(thisHash, Pair.of(1, donationDto));
}
});
Set<String> pageHashesToRemove = new HashSet<>();
donationFromPageHashes.entrySet().forEach(pageHash -> {
if(existingHashes.containsKey(pageHash.getKey())) {
Integer existingDonation = existingHashes.get(pageHash.getKey());
int amountDifference = pageHash.getValue().getKey() - existingDonation;
if(amountDifference == 0) {
pageHashesToRemove.add(pageHash.getKey()); // it matches 1:1, we know about all of them already
} if(amountDifference < 0) {
pageHashesToRemove.add(pageHash.getKey());
log.warn("We have more donations than on the page of hash {}:{}.", pageHash.getKey(), amountDifference);
} else {
pageHash.setValue(Pair.of(amountDifference, pageHash.getValue().getRight()));
}
}
});
pageHashesToRemove.forEach(donationFromPageHashes::remove);
if(donationFromPageHashes.isEmpty()) {
log.info("No new donations - ending search.");
return;
}
List<CompletableFuture<Void>> notificationFutures = new ArrayList<>();
donationFromPageHashes.values().forEach(donationInfo -> {
for (int i = 0; i < donationInfo.getLeft(); i++) {
DonationDto donationDto = donationInfo.getRight();
DonationResponseModel model = DonationResponseModel
.builder()
.message(donationDto.getText())
.donatorName(donationDto.getName())
.amount(donationDto.getAmount())
.anonymous(donationDto.getAnonymous())
.build();
notificationFutures.add(sendDonationNotification(model));
}
});
new CompletableFutureList<>(notificationFutures).getMainFuture().thenAccept(unused -> {
log.info("All {} notifications send.", notificationFutures.size());
}).exceptionally(throwable -> {
log.warn("Failed to send notifications about {} new donations.", notificationFutures.size(), throwable);
return null;
});
log.info("Creating/updating {} donation entries.", donationFromPageHashes.size());
allDonations.forEach(donation -> {
Set<String> donationsToRemoveBecauseUpdate = new HashSet<>();
donationFromPageHashes.forEach((key, value) -> {
// its assumed that donationFromPageHashes only contains donations that need to be created
if (donation.getId().equals(key)) {
donation.setCount(value.getLeft() + donation.getCount());
donationManagementServiceBean.updateDonation(donation);
donationsToRemoveBecauseUpdate.add(key);
}
});
donationsToRemoveBecauseUpdate.forEach(donationFromPageHashes::remove);
});
donationFromPageHashes.forEach((key, value) ->
donationManagementServiceBean.saveDonation(key, value.getLeft()));
}
public CompletableFuture<Void> sendDonationNotification(DonationResponseModel donation) {
Long targetServerId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME));
DonationsModel donationInfoModel = getDonationInfoModel(targetServerId);
DonationsModel donationInfoModel = getDonationInfoModel();
DonationNotificationModel model = DonationNotificationModel
.builder()
.donation(donation)

View File

@@ -0,0 +1,33 @@
package dev.sheldan.sissi.module.debra.service.management;
import dev.sheldan.sissi.module.debra.model.database.Donation;
import dev.sheldan.sissi.module.debra.repository.DonationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class DonationManagementServiceBean {
@Autowired
private DonationRepository donationRepository;
public List<Donation> getAllDonations() {
return donationRepository.findAll();
}
public void updateDonation(Donation donation) {
donationRepository.save(donation);
}
public Donation saveDonation(String hash, Integer count) {
Donation donation = Donation
.builder()
.id(hash)
.count(count)
.build();
return donationRepository.save(donation);
}
}

View File

@@ -4,8 +4,7 @@ abstracto.featureFlags.debra.enabled=false
abstracto.postTargets.debraDonationNotification.name=debraDonationNotification
abstracto.postTargets.debraDonationNotification2.name=debraDonationNotification2
sissi.debra.websocketURL=ws://spenden.baba.fm:8765/
sissi.debra.donationAPIUrl=https://www.altruja.de/api/page/discord-schmetterlingsaktion-2024?details=1&num=%s&ort=0
sissi.debra.donationPageUrl=https://secure.sicherhelfen.org/campaigns/07a3baf6-5cdc-4300-854b-ea2b36b0b218/show
abstracto.systemConfigs.debraDonationNotificationDelayMillis.name=debraDonationNotificationDelayMillis
abstracto.systemConfigs.debraDonationNotificationDelayMillis.longValue=60000

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="donation_fetch_job-insert">
<insert tableName="scheduler_job">
<column name="name" value="donationFetchJob"/>
<column name="group_name" value="debra"/>
<column name="clazz" value="dev.sheldan.sissi.module.debra.job.DonationFetchJob"/>
<column name="active" value="true"/>
<column name="cron_expression" value="0 */2 * * * ?"/>
<column name="recovery" value="false"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

@@ -0,0 +1,28 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="donation-table">
<createTable tableName="donation">
<column name="id" type="TEXT">
<constraints nullable="false"/>
</column>
<column name="count" type="NUMERIC">
<constraints nullable="false"/>
</column>
<column name="created" type="TIMESTAMP WITHOUT TIME ZONE">
<constraints nullable="false"/>
</column>
<column name="updated" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<sql>
DROP TRIGGER IF EXISTS donation_update_trigger ON donation;
CREATE TRIGGER donation_update_trigger BEFORE UPDATE ON donation FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure();
</sql>
<sql>
DROP TRIGGER IF EXISTS donation_insert_trigger ON donation;
CREATE TRIGGER donation_insert_trigger BEFORE INSERT ON donation FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure();
</sql>
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -5,4 +5,5 @@
<include file="1.3.6/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.21/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.29/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.16/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -166,6 +166,7 @@ public class CreateMeetup extends AbstractConditionableCommand {
Parameter topicParameter = Parameter
.builder()
.templated(true)
.validators(Arrays.asList(MaxStringLengthValidator.max(256)))
.name(TOPIC_PARAMETER)
.type(String.class)
.build();
@@ -174,6 +175,7 @@ public class CreateMeetup extends AbstractConditionableCommand {
.builder()
.templated(true)
.name(DESCRIPTION_PARAMETER)
.validators(Arrays.asList(MaxStringLengthValidator.max(2048)))
.remainder(true)
.optional(true)
.type(String.class)
@@ -184,6 +186,7 @@ public class CreateMeetup extends AbstractConditionableCommand {
.templated(true)
.name(LOCATION_PARAMETER)
.remainder(true)
.validators(Arrays.asList(MaxStringLengthValidator.max(100)))
.optional(true)
.slashCommandOnly(true)
.type(String.class)

View File

@@ -23,6 +23,7 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -49,19 +50,6 @@ public class NotifyMeetupParticipants extends AbstractConditionableCommand {
private static final String NOTIFY_MEETUP_PARTICIPANTS_COMMAND = "notifyMeetupParticipants";
private static final String NOTIFY_MEETUP_PARTICIPANTS_RESPONSE = "notifyMeetupParticipants_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
Long meetupId = (Long) parameters.get(0);
Meetup meetup = meetupManagementServiceBean.getMeetup(meetupId, commandContext.getGuild().getIdLong());
if(!meetup.getOrganizer().getUserReference().getId().equals(commandContext.getAuthor().getIdLong())) {
throw new NotMeetupOrganizerException();
}
String notificationMessage = (String) parameters.get(1);
return meetupServiceBean.notifyMeetupParticipants(meetup, notificationMessage, null)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
Long meetupId = slashCommandParameterService.getCommandOption(MEETUP_ID_PARAMETER, event, Integer.class).longValue();
@@ -69,12 +57,15 @@ public class NotifyMeetupParticipants extends AbstractConditionableCommand {
if(!meetup.getOrganizer().getUserReference().getId().equals(event.getMember().getIdLong())) {
throw new NotMeetupOrganizerException();
}
MeetupDecision toNotify = null;
if(slashCommandParameterService.hasCommandOption(NOTIFICATION_MEETUP_DECISION, event)) {
toNotify = MeetupDecision.valueOf(slashCommandParameterService.getCommandOption(NOTIFICATION_MEETUP_DECISION, event, String.class));
List<MeetupDecision> decisionsToNotify = new ArrayList<>();
for (int i = 0; i < MeetupDecision.values().length; i++) {
if(slashCommandParameterService.hasCommandOption(NOTIFICATION_MEETUP_DECISION + "_" + i, event)) {
String choice = slashCommandParameterService.getCommandOption(NOTIFICATION_MEETUP_DECISION + "_" + i, event, String.class);
decisionsToNotify.add(MeetupDecision.valueOf(choice));
}
}
String notificationMessage = slashCommandParameterService.getCommandOption(NOTIFICATION_MESSAGE_PARAMETER, event, String.class);
return meetupServiceBean.notifyMeetupParticipants(meetup, notificationMessage, toNotify)
return meetupServiceBean.notifyMeetupParticipants(meetup, notificationMessage, decisionsToNotify)
.thenCompose(unused -> interactionService.replyEmbed(NOTIFY_MEETUP_PARTICIPANTS_RESPONSE, event))
.thenApply(unused -> CommandResult.fromSuccess());
}
@@ -106,6 +97,8 @@ public class NotifyMeetupParticipants extends AbstractConditionableCommand {
.templated(true)
.name(NOTIFICATION_MEETUP_DECISION)
.type(String.class)
.listSize(MeetupDecision.values().length)
.isListParam(true)
.optional(true)
.choices(meetupDecisions)
.slashCommandOnly(true)
@@ -131,6 +124,7 @@ public class NotifyMeetupParticipants extends AbstractConditionableCommand {
.templated(true)
.slashCommandConfig(slashCommandConfig)
.async(true)
.slashCommandOnly(true)
.supportsEmbedException(true)
.causesReaction(true)
.parameters(parameters)

View File

@@ -60,7 +60,7 @@ public class MeetupDecisionListener implements ButtonClickedListener {
MeetupMessageModel meetupMessageModel = meetupServiceBean.getMeetupMessageModel(meetup);
addParticipationToModel(meetupMessageModel, userInAServer, payload.getMeetupDecision());
MessageToSend messageToSend = meetupServiceBean.getMeetupMessage(meetupMessageModel, model.getServerId());
channelService.editEmbedMessageInAChannel(messageToSend.getEmbeds().get(0), model.getEvent().getChannel(), meetup.getMessageId())
channelService.editMessageInAChannelFuture(messageToSend, model.getEvent().getChannel(), meetup.getMessageId())
.thenAccept(message -> log.info("Updated message of meetup {} in channel {} in server {}.", meetup.getId().getId(), meetup.getMeetupChannel().getId(), meetup.getServer().getId()))
.exceptionally(throwable -> {
log.info("Failed to update message of meetup {} in channel {} in server {}.", meetup.getId().getId(), meetup.getMeetupChannel().getId(), meetup.getServer().getId(), throwable);

View File

@@ -293,8 +293,8 @@ public class MeetupServiceBean {
}
}
public CompletableFuture<Void> notifyMeetupParticipants(Meetup meetup, String message, MeetupDecision toNotify) {
List<MeetupDecision> decisionsToBeNotified = toNotify == null ? Arrays.asList(MeetupDecision.MAYBE, MeetupDecision.YES) : Arrays.asList(toNotify);
public CompletableFuture<Void> notifyMeetupParticipants(Meetup meetup, String message, List<MeetupDecision> toNotify) {
List<MeetupDecision> decisionsToBeNotified = toNotify == null || toNotify.isEmpty() ? Arrays.asList(MeetupDecision.MAYBE, MeetupDecision.YES) : toNotify;
List<MemberDisplay> participants = meetup
.getParticipants()
.stream()
@@ -372,7 +372,7 @@ public class MeetupServiceBean {
List<Long> userIdsToNotify = participants
.stream()
.map(meetupParticipator -> meetupParticipator.getParticipator().getUserReference().getId())
.collect(Collectors.toList());
.toList();
Long serverId = meetup.getServer().getId();
@@ -415,7 +415,7 @@ public class MeetupServiceBean {
List<Long> userInServerIds = participants
.stream()
.map(meetupParticipant -> meetupParticipant.getParticipator().getUserInServerId())
.collect(Collectors.toList());
.toList();
meetup
.getParticipants().removeIf(meetupParticipant -> userInServerIds.contains(meetupParticipant.getParticipator().getUserInServerId()));
MeetupMessageModel meetupMessageModel = getMeetupMessageModel(meetup);

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>sissi-modules</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>application</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,6 +14,7 @@
<module>quotes</module>
<module>meetup</module>
<module>debra</module>
<module>miepscord</module>
<module>rss-news</module>
</modules>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.application</groupId>
<artifactId>sissi-modules</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -23,6 +23,7 @@ import dev.sheldan.sissi.module.quotes.config.QuotesModuleDefinition;
import dev.sheldan.sissi.module.quotes.exception.QuoteNotFoundException;
import dev.sheldan.sissi.module.quotes.model.database.Quote;
import dev.sheldan.sissi.module.quotes.service.QuoteServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -37,6 +38,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class QuoteCommand extends AbstractConditionableCommand {
private static final String QUOTE_COMMAND = "quote";
@@ -76,6 +78,7 @@ public class QuoteCommand extends AbstractConditionableCommand {
foundQuote = quoteServiceBean.getRandomQuoteForMember(user);
}
Quote quoteToDisplay = foundQuote.orElseThrow(QuoteNotFoundException::new);
log.info("Displaying quote {} in server {}.", quoteToDisplay.getId(), quoteToDisplay.getServer().getId());
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.sendMessageToChannel(messageToSend, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
@@ -98,6 +101,7 @@ public class QuoteCommand extends AbstractConditionableCommand {
foundQuote = quoteServiceBean.getRandomQuote(server);
}
Quote quoteToDisplay = foundQuote.orElseThrow(QuoteNotFoundException::new);
log.info("Displaying quote {} in server {}.", quoteToDisplay.getId(), quoteToDisplay.getServer().getId());
return quoteServiceBean.renderQuoteToMessageToSend(quoteToDisplay)
.thenCompose(messageToSend -> self.replyMessage(event, messageToSend))
.thenApply(unused -> CommandResult.fromSuccess());

View File

@@ -1,6 +1,8 @@
package dev.sheldan.sissi.module.quotes.model.command;
import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import lombok.Builder;
import lombok.Getter;
@@ -11,14 +13,14 @@ import java.util.List;
@Builder
public class QuoteResponseModel {
private Long quoteId;
private String authorAvatarURL;
private String authorName;
private UserDisplay authorUserDisplay;
private MemberDisplay authorMemberDisplay;
private ServerChannelMessage quotedMessage;
private String quoteContent;
private List<String> imageAttachmentURLs;
private List<String> mediaAttachmentURLs;
private List<String> fileAttachmentURLs;
private String adderAvatarURL;
private String adderName;
private UserDisplay adderUserDisplay;
private MemberDisplay adderMemberDisplay;
private Instant creationDate;
private String sourceChannelName;
}

View File

@@ -41,8 +41,8 @@ public class QuoteAttachment {
@Getter
@Setter
@Column(name = "is_image", nullable = false)
@Column(name = "is_media", nullable = false)
@Builder.Default
private Boolean isImage = false;
private Boolean isMedia = false;
}

View File

@@ -4,6 +4,8 @@ import dev.sheldan.abstracto.core.models.ServerChannelMessage;
import dev.sheldan.abstracto.core.models.ServerUser;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
import dev.sheldan.abstracto.core.models.template.display.MemberDisplay;
import dev.sheldan.abstracto.core.models.template.display.UserDisplay;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.MemberService;
import dev.sheldan.abstracto.core.service.UserService;
@@ -103,21 +105,21 @@ public class QuoteServiceBean {
List<String> imageAttachments = quote
.getAttachments()
.stream()
.filter(QuoteAttachment::getIsImage)
.filter(QuoteAttachment::getIsMedia)
.map(QuoteAttachment::getUrl)
.collect(Collectors.toList());
List<String> fileAttachments = quote
.getAttachments()
.stream()
.filter(quoteAttachment -> !quoteAttachment.getIsImage())
.filter(quoteAttachment -> !quoteAttachment.getIsMedia())
.map(QuoteAttachment::getUrl)
.collect(Collectors.toList());
QuoteResponseModel.QuoteResponseModelBuilder modelBuilder = QuoteResponseModel
.builder()
.quoteContent(quote.getText())
.imageAttachmentURLs(imageAttachments)
.mediaAttachmentURLs(imageAttachments)
.quoteId(quote.getId())
.fileAttachmentURLs(fileAttachments)
.creationDate(quote.getCreated())
@@ -166,42 +168,14 @@ public class QuoteServiceBean {
.filter(user -> user.getIdLong() == quoteAdderUserId)
.findFirst()
.orElse(null);
String adderAvatar = Optional
.ofNullable(adderMember)
.map(member -> member.getUser().getAvatarUrl())
.orElse(Optional
.ofNullable(adderUser)
.map(User::getAvatarUrl)
.orElse(null));
String authorAvatar = Optional
.ofNullable(authorMember)
.map(member -> member.getUser().getAvatarUrl())
.orElse(Optional
.ofNullable(authorUser)
.map(User::getAvatarUrl)
.orElse(null));
String adderName = Optional
.ofNullable(adderMember)
.map(Member::getEffectiveName)
.orElse(Optional
.ofNullable(adderUser)
.map(User::getName)
.orElse(null));
String authorName = Optional
.ofNullable(authorMember)
.map(Member::getEffectiveName)
.orElse(Optional
.ofNullable(authorUser)
.map(User::getName)
.orElse(null));
String channelName = sourceChannel
.map(Channel::getName)
.orElse(null);
QuoteResponseModel model = modelBuilder
.adderAvatarURL(adderAvatar)
.authorAvatarURL(authorAvatar)
.adderName(adderName)
.authorName(authorName)
.authorMemberDisplay(authorMember != null ? MemberDisplay.fromMember(authorMember) : null)
.authorUserDisplay(authorUser != null ? UserDisplay.fromUser(authorUser) : UserDisplay.fromServerUser(ServerUser.fromId(serverId, quotedUserId)))
.adderMemberDisplay(adderMember != null ? MemberDisplay.fromMember(adderMember) : null)
.adderUserDisplay(adderUser != null ? UserDisplay.fromUser(adderUser) : UserDisplay.fromServerUser(ServerUser.fromId(serverId, quoteAdderUserId)))
.sourceChannelName(channelName)
.build();
return templateService.renderEmbedTemplate(QUOTE_RESPONSE_TEMPLATE_KEY, model, serverId);
@@ -260,7 +234,7 @@ public class QuoteServiceBean {
List<Pair<String, Boolean>> attachments = quoteMessage
.getAttachments()
.stream()
.map(attachment -> Pair.of(attachment.getProxyUrl(), attachment.isImage()))
.map(attachment -> Pair.of(attachment.getProxyUrl(), attachment.isImage() || attachment.isVideo()))
.toList();
return quoteManagementService.createQuote(author, adder, quoteMessage.getContentDisplay(), ServerChannelMessage.fromMessage(quoteMessage), attachments);
}

View File

@@ -45,7 +45,7 @@ public class QuoteManagementService {
.url(stringBooleanPair.getLeft())
.quote(quote)
.server(adder.getServerReference())
.isImage(stringBooleanPair.getRight())
.isMedia(stringBooleanPair.getRight())
.build())
.toList();

View File

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

View File

@@ -0,0 +1,12 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd" >
<changeSet author="Sheldan" id="quote_attachment-rename-media-column">
<renameColumn
tableName="quote_attachment"
newColumnName="is_media"
oldColumnName="is_image" />
</changeSet>
</databaseChangeLog>

View File

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

View File

@@ -5,4 +5,5 @@
<include file="1.0.2/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.56/collection.xml" relativeToChangelogFile="true"/>
<include file="1.4.57/collection.xml" relativeToChangelogFile="true"/>
<include file="1.5.6/collection.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>sissi-modules</artifactId>
<groupId>dev.sheldan.sissi.application</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -2,4 +2,4 @@ apiVersion: v2
name: sissi
description: A Helm chart for Kubernetes
type: application
version: 1.5.2
version: 1.5.15

View File

@@ -60,7 +60,7 @@ spec:
- name: DB_SCHEMA
value: {{ .Values.dbCredentials.schema }}
- name: DEBRA_DONATION_NOTIFICATION_SERVER_ID
value: "297910194841583616"
value: "{{ .Values.bot.config.debraNotificationServerId }}"
- name: WEEKLY_TEXT_SERVER_ID
value: "{{ .Values.bot.config.weeklyTextServerId }}"
- name: TOKEN

View File

@@ -3,7 +3,7 @@ bot:
repository: harbor.sheldan.dev/sissi
pullPolicy: Always
image: sissi-bot
tag: 1.5.2
tag: 1.5.15
livenessProbe:
initialDelaySeconds: 60
periodSeconds: 5
@@ -17,12 +17,13 @@ bot:
host: null
config:
weeklyTextServerId: null
debraNotificationServerId: null
restApi:
enabled: true
repository: harbor.sheldan.dev/sissi
pullPolicy: Always
image: sissi-rest-api
tag: 1.5.2
tag: 1.5.15
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
@@ -62,7 +63,7 @@ privateRestApi:
repository: harbor.sheldan.dev/sissi
pullPolicy: Always
image: sissi-private-rest-api
tag: 1.5.2
tag: 1.5.15
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
@@ -93,23 +94,23 @@ templateDeployment:
repository: harbor.sheldan.dev/abstracto
pullPolicy: Always
image: abstracto-template-deployment
tag: 1.6.4
tag: 1.6.17
templateDeploymentData:
repository: harbor.sheldan.dev/sissi
pullPolicy: Always
image: sissi-template-data
tag: 1.5.2
tag: 1.5.15
dbConfigDeployment:
enabled: true
repository: harbor.sheldan.dev/abstracto
pullPolicy: Always
image: abstracto-db-deployment
tag: 1.6.4
tag: 1.6.17
dbConfigDeploymentData:
repository: harbor.sheldan.dev/sissi
pullPolicy: Always
image: sissi-db-data
tag: 1.5.2
tag: 1.5.15
dbCredentials:
host: null
port: null

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>deployment</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -885,6 +885,16 @@
<destFileName>logging-template-overrides.zip</destFileName>
</artifactItem>
<artifactItem>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>starboard-template-overrides</artifactId>
<version>${project.version}</version>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>${file.basedir}/template-data/template-artifacts/</outputDirectory>
<destFileName>starboard-template-overrides.zip</destFileName>
</artifactItem>
<!-- overrides translations -->
<artifactItem>

View File

@@ -1,4 +1,4 @@
FROM alpine:3.19.1
FROM alpine:3.22.1
MAINTAINER Sheldan
ADD config/ /var/db-config/
ADD liquibase-artifacts /var/db-config/liquibase-zips

View File

@@ -4,7 +4,7 @@ ARG VERSION
ARG ABSTRACTO_VERSION
FROM ${REGISTRY_PREFIX}abstracto-rest-api-image-gen:${ABSTRACTO_VERSION:-latest} AS image-gen-api
FROM ${SISSI_REGISTRY_PREFIX}sissi-image-gen-api:${VERSION:-latest} AS sissi-image-gen-api
FROM image_gen AS sissi-image-gen-api
FROM ${REGISTRY_PREFIX}abstracto-rest-api:${ABSTRACTO_VERSION:-latest} AS running-image
COPY --from=image-gen-api /python /python

View File

@@ -2,7 +2,7 @@ ARG REGISTRY_PREFIX
ARG SISSI_REGISTRY_PREFIX
ARG VERSION
ARG ABSTRACTO_VERSION
FROM ${SISSI_REGISTRY_PREFIX}sissi-debra-rest-api:${VERSION:-latest} AS debra-api
FROM debra_rest AS debra-api
FROM ${REGISTRY_PREFIX}abstracto-rest-api-experience:${ABSTRACTO_VERSION:-latest} AS experience-api
FROM ${REGISTRY_PREFIX}abstracto-rest-api-core:${ABSTRACTO_VERSION:-latest} AS core-api

View File

@@ -12,7 +12,7 @@ RUN $JAVA_HOME/bin/jlink \
--compress=2 \
--output /customjre
FROM alpine:3.21
FROM alpine:3.22.1
ENV JAVA_HOME=/jre
ENV PATH="${JAVA_HOME}/bin:${PATH}"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.19.1
FROM alpine:3.22.1
MAINTAINER Sheldan
ADD config /var/template-config/
ADD template-artifacts /var/template-config/templates

View File

@@ -5,7 +5,7 @@
"image-generation",
"quotes", "meetup", "debra", "rss-news", "miepscord",
"moderation-custom", "image-generation-custom",
"moderation-template-overrides", "experience-template-overrides", "logging-template-overrides"
"moderation-template-overrides", "experience-template-overrides", "logging-template-overrides", "starboard-template-overrides"
],
"translation_artifacts": [
"core",

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,4 +1,3 @@
version: "3.7"
services:
bot-packaging:
@@ -23,9 +22,9 @@ services:
REGISTRY_PREFIX: ${ABSTRACTO_PREFIX}
VERSION: ${VERSION}
ABSTRACTO_VERSION: ${ABSTRACTO_VERSION}
additional_contexts:
debra_rest: "service:debra-rest-api"
image: ${REGISTRY_PREFIX}sissi-rest-api:${VERSION:-latest}
depends_on:
- debra-rest-api
private-rest-api-packaging:
build:
context: deployment/image-packaging/src/main/docker/private-rest-api
@@ -34,9 +33,9 @@ services:
SISSI_REGISTRY_PREFIX: ${REGISTRY_PREFIX}
ABSTRACTO_VERSION: ${ABSTRACTO_VERSION}
VERSION: ${VERSION}
additional_contexts:
image_gen: "service:image-gen-api"
image: ${REGISTRY_PREFIX}sissi-private-rest-api:${VERSION:-latest}
depends_on:
- image-gen-api
db-data:
build:
context: deployment/image-packaging/src/main/docker/db-data

10
pom.xml
View File

@@ -13,22 +13,22 @@
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<name>Sissi</name>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<abstracto.version>1.6.4</abstracto.version>
<abstracto.templates.version>1.4.55</abstracto.templates.version>
<abstracto.version>1.6.17</abstracto.version>
<abstracto.templates.version>1.4.63</abstracto.templates.version>
<apache-jena.version>4.9.0</apache-jena.version>
<rssreader.version>3.5.0</rssreader.version>
<jsoup.version>1.21.2</jsoup.version>
</properties>
<modules>
<module>application</module>
<module>templates</module>
<module>deployment</module>
<module>application/sissi-modules/miepscord</module>
</modules>
<repositories>
@@ -73,7 +73,7 @@
<connection>scm:git:${project.scm.url}</connection>
<developerConnection>scm:git:${project.scm.url}</developerConnection>
<url>https://github.com/Sheldan/Sissi.git</url>
<tag>HEAD</tag>
<tag>sissi-1.5.16</tag>
</scm>
</project>

View File

@@ -0,0 +1,76 @@
{
<#assign mapping={
'bp': '340380695585095680',
'np': '400642855682572288',
'bk': '299162473775366144',
'mi': '400642854248120330',
'na': '400642590267146240',
'br': '400642587620278273',
'lo': '400643490020851712',
'lr': '400642584617418753',
'lm': '400642579881787402',
'bo': '400642539889229825',
'bm': '299162333668835328',
'gm': '400642460713484308',
'up': '299162129284726784',
'ap': '336786889589915659',
'dd': '336786578103992323',
'dr': '298790326943088670',
'ms': '298790213453479937',
'bs': '298790020192403456',
'di': '298790014974820356',
'in': '298789520084566018'
}>
<#assign groups={
'1': {'bp': 1, 'np': 2, 'bk': 3, 'mi': 4, 'na': 5, 'br': 6, 'lo': 7, 'lr': 8, 'lm': 9, 'bo': 10, 'bm': 11, 'gm': 12},
'2': {'up': 1, 'ap': 2},
'3': {'dd': 1, 'dr': 2},
'4': {'ms': 1, 'bs': 2},
'5': {'di': 1},
'6': {'in': 1}
}>
<#assign allowedRoleIds=[]>
<#assign reverseMapping={}>
<#list mapping as key, value>
<#assign allowedRoleIds=allowedRoleIds + [value]>
<#assign reverseMapping=reverseMapping + {value: key}>
</#list>
<#assign relevantRoles={}>
<#assign relevantRoleIds=[]>
<#list roles as role>
<#if allowedRoleIds?seq_contains(role.roleId?c)>
<#assign relevantRoles=relevantRoles + {role.roleId?c:role.roleName}>
<#assign relevantRoleIds=relevantRoleIds+[role.roleId?c]>
</#if>
</#list>
<#function is_printed role_id>
<#assign group={}>
<#assign found_prio=1>
<#list groups as key,members>
<#list members as roleshortcut,prio>
<#if role_id=mapping[roleshortcut]>
<#assign group=members>
<#assign found_prio=prio>
</#if>
</#list>
</#list>
<#assign print=true>
<#list group as shortcut,prio>
<#if relevantRoleIds?seq_contains(mapping[shortcut])>
<#if prio < found_prio>
<#assign print=false>
</#if>
</#if>
</#list>
<#return print>
</#function>
<#macro get_title key>
<#assign role_id=mapping[key]><#rt>
<#if is_printed(role_id) && relevantRoleIds?seq_contains(role_id)><#rt>
${relevantRoles[role_id]}<#rt>
</#if>
</#macro>
<#include "user_detail">
<#assign userText><@get_title 'bp'/><@get_title 'np'/><@get_title 'bk'/><@get_title 'mi'/><@get_title 'na'/><@get_title 'br'/><@get_title 'lo'/><@get_title 'lr'/><@get_title 'lm'/><@get_title 'bo'/><@get_title 'bm'/><@get_title 'gm'/><@get_title 'up'/><@get_title 'ap'/><@get_title 'dd'/><@get_title 'dr'/><@get_title 'di'/><@get_title 'in'/> ${user.name} <@get_title 'ms'/><@get_title 'bs'/> (${user.id?c})</#assign>
"additionalMessage": "<#compress><@safe_include "user_left_text"/></#compress>"
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi</groupId>
<artifactId>sissi</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -4,7 +4,7 @@
<parent>
<artifactId>customization-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<artifactId>image-generation-customization-templates</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>customization-templates</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>sissi-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>module-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -13,7 +13,7 @@
,"fields": [
<#list donations as donation>
{
"name": "<#if donation.anonymous><#include "donations_response_anonymous"><#else>${donation.firstName}</#if>",
"name": "<#if donation.anonymous><#include "donations_response_anonymous"><#else>${donation.name}</#if>",
"value": "${donation.donationAmount}€",
"inline": true
}

View File

@@ -2,12 +2,14 @@
"embeds": [
{
"title": {
<#assign donatorName=donation.donatorName>
<#assign donatorName><#if donation.anonymous><#include "donations_response_anonymous"><#else>${donation.donatorName}</#if></#assign>
<#assign donationAmount=donation.amount>
"title": "<@safe_include "debra_donation_notification_embed_title"/>"
},
<#assign donationMessage=donation.message>
"description": "${donationMessage?json_string}",
<#if donation.message != 'gespendet'>
<#assign donationMessage=donation.message>
"description": "${donationMessage?json_string}",
</#if>
"fields": [
{
<#assign totalDonationAmount=totalDonationAmount>
@@ -24,7 +26,7 @@
"buttons": [
{
"label": "<@safe_include "debra_donation_notification_link_button_label"/>",
"url": "http://tiny.cc/schmetterling2024",
"url": "https://tinyurl.com/debra25",
"buttonStyle": "link",
"metaConfig": {
"persistCallback": false

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>module-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,33 +1,30 @@
<#include "format_instant">
{
"embeds": [
"components": [
{
<#include "abstracto_color">,
"title": {
"title": "${topic?json_string}"
},
"description": "<@format_instant_long_date_time instant=meetupTime/>
${description?json_string}"
<#if location?? && location != "%22%22">,
"fields": [
{
"name": "<@safe_include "createMeetup_confirmation_location_field_title"/>",
"value": "https://www.google.com/maps?q=${location?json_string}"
}
]
</#if>
}
],
"buttons": [
{
"label": "<@safe_include "createMeetup_confirm_button_label"/>",
"id": "${confirmationId}",
"buttonStyle": "success"
<#assign hasLocation=location?? && location != "%22%22">
"type": "textDisplay",
"content": "<#include "createMeetup_meetup_information">"
},
{
"label": "<@safe_include "createMeetup_cancel_button_label"/>",
"id": "${cancelId}",
"buttonStyle": "danger"
"type": "actionRow",
"actionRowItems": [
{
"label": "<@safe_include "createMeetup_confirm_button_label"/>",
"id": "${confirmationId}",
"buttonStyle": "success",
"type": "button"
},
{
"label": "<@safe_include "createMeetup_cancel_button_label"/>",
"id": "${cancelId}",
"buttonStyle": "danger",
"type": "button"
}
]
}
]
],
"messageConfig": {
"useComponentsV2": true
}
}

View File

@@ -1,10 +1,36 @@
<#include "format_instant">
{
"embeds": [
"components": [
<#list meetups as meetup>
<#assign meetup=meetup>
<#assign topic=meetup.topic>
<#assign time><@format_instant_long_date_time instant=meetup.meetupTime/>
</#assign><#assign timeRelative><@format_instant_relative instant=meetup.meetupTime/></#assign>
<#assign link=meetup.meetupMessage.jumpUrl>
{
<#include "abstracto_color">,
"description": "<#list meetups as meetup><#assign meetup=meetup><#assign topic=meetup.topic><#assign time><@format_instant_long_date_time instant=meetup.meetupTime/></#assign><#assign timeRelative><@format_instant_relative instant=meetup.meetupTime/></#assign><#assign link=meetup.meetupMessage.jumpUrl><#include "meetup_list_meetup_display">
<#else><#include "meetup_list_no_meetups"></#list>"
"type": "section",
"components": [
{
"type": "textDisplay",
"content": "<#include "meetup_list_meetup_display">"
}
]
,"accessory": {
"type": "button",
"label": "<#include "meetup_list_jump_button_label"/>",
"url": "${link}",
"buttonStyle": "link"
}
}
]
<#sep>,</#sep>
<#else>
{
"type": "textDisplay",
"content": "<#include "meetup_list_no_meetups">"
}
</#list>
],
"messageConfig": {
"useComponentsV2": true
}
}

View File

@@ -1,63 +1,78 @@
<#include "format_instant">
{
<#assign roleMention="<@&371419588619141121>"/>
"additionalMessage": "${roleMention?json_string}",
"embeds": [
"components": [
{
"type": "textDisplay",
<#assign roleMention="<@&371419588619141121>"/>
"content": "<#if cancelled>~~</#if>${roleMention?json_string} ${topic?json_string} - <@safe_include "meetup_message_id_display"/><#if cancelled>~~</#if>"
},
<#if description?has_content>
{
<#assign descriptionText>${description?json_string}</#assign>
<#assign organizerText>${organizer.memberMention}</#assign>
"type": "textDisplay",
"content": "<#if cancelled>~~</#if><@safe_include "meetup_description_component"/><#if cancelled>~~</#if>"
},
</#if>
{
<#include "abstracto_color">,
"title": {
"title": "${topic?json_string} - <@safe_include "meetup_message_id_display"/>"
},
<#assign time><@format_instant_long_date_time instant=meetupTime/></#assign>
<#assign timeRelative><@format_instant_relative instant=meetupTime/></#assign>
<#assign organizerText>${organizer.memberMention}</#assign>
<#assign meetupId=meetupId/>
<#assign descriptionText>${description?json_string}</#assign>
<#assign participantsText> (${participants?size}) <#list participants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign maybeParticipantsText> (${maybeParticipants?size}) <#list maybeParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign noTimeParticipantsText> (${noTimeParticipants?size}) <#list noTimeParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign declinedParticipantsText> (${declinedParticipants?size}) <#list declinedParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
"description": "<#if cancelled>~~</#if><@safe_include "meetup_display_description"/><#if cancelled>~~</#if>"
}
],
"buttons": [
{
"label": "<@safe_include "meetup_message_yes_button_label"/>",
"id": "${yesId}",
"buttonStyle": "success"
},
{
"label": "<@safe_include "meetup_message_maybe_button_label"/>",
"id": "${maybeId}",
"buttonStyle": "secondary"
},
{
"label": "<@safe_include "meetup_message_no_time_button_label"/>",
"id": "${noTimeId}",
"buttonStyle": "danger"
},
{
"label": "<@safe_include "meetup_message_no_button_label"/>",
"id": "${noId}",
"buttonStyle": "danger"
}
<#if location?? && location != "%22%22">,
{
"label": "<@safe_include "meetup_message_location_button_label"/>",
"url": "https://www.google.com/maps?q=${location?json_string}",
"buttonStyle": "link"
<#if location?? && location != "%22%22">
"type": "section",
"components": [
{
"type": "textDisplay",
"content": "<@safe_include "meetup_display_time_component"/>"
}
],
"accessory": {
"type": "button",
"label": "<@safe_include "meetup_message_location_button_label"/>",
"url": "https://www.google.com/maps?q=${location?json_string}",
"buttonStyle": "link"
}
<#else>
"type": "textDisplay",
"content": "<@safe_include "meetup_display_time_component"/>"
</#if>
}
<#macro decision_component button_id button_style label_template content_template user>
{
"type": "section",
"components": [
{
"type": "textDisplay",
"content": "<@safe_include content_template/>"
}
]
,"accessory": {
"type": "button",
"id": "${button_id}",
"label": "<@safe_include label_template/>",
"buttonStyle": "${button_style}"
}
}
</#macro>
<#assign participantsText> (${participants?size}) <#list participants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign maybeParticipantsText> (${maybeParticipants?size}) <#list maybeParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign noTimeParticipantsText> (${noTimeParticipants?size}) <#list noTimeParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
<#assign declinedParticipantsText> (${declinedParticipants?size}) <#list declinedParticipants as member>${member.memberMention}<#sep>, </#sep><#else><#include "meetup_message_no_member"></#list></#assign>
,<@decision_component yesId "success" "meetup_message_yes_button_label" "meetup_user_display_participants" participantsText/>
,<@decision_component maybeId "secondary" "meetup_message_maybe_button_label" "meetup_user_display_maybe_participants" maybeParticipantsText/>
,<@decision_component noId "danger" "meetup_message_no_button_label" "meetup_user_display_declined_participants" declinedParticipantsText/>
,<@decision_component noTimeId "danger" "meetup_message_no_time_button_label" "meetup_user_display_no_time_participants" noTimeParticipantsText/>
<#if meetupIcsModel.attachIcsFile>
,{
"type": "fileDisplay",
"fileName": "<@safe_include "meetup_ics_file_name"/>.ics",
"fileContent": "<@safe_include "meetup_ice_file_download"/>"
}
</#if>
],
<#if meetupIcsModel.attachIcsFile>
"files": [
{
"fileName": "<@safe_include "meetup_ics_file_name"/>.ics",
"fileContent": "<@safe_include "meetup_ice_file_download"/>"
}
],
</#if>
"messageConfig": {
"allowsRoleMention": true
"allowsRoleMention": true,
"allowsUserMention": false,
"useComponentsV2": true
}
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>module-templates</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<artifactId>miepscord-templates</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>sissi-templates</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>module-templates</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,26 +1,83 @@
<#include "format_instant">
{
"embeds": [
"components": [
{
<#include "abstracto_color">,
"author": {
<#assign authorName><@default_template_if_null authorName "quote_response_default_author_name"/></#assign>
<#assign channelName><@default_template_if_null sourceChannelName "quote_response_default_channel_name"/></#assign>
"name": "<@safe_include "quote_response_header_author_name"/>"
<#if authorAvatarURL??>,"avatar": "${authorAvatarURL}"</#if>
},
<#assign quoteId=quoteId>
<#assign quoteDescription=quoteContent>
<#assign quoteJumpUrl=quotedMessage.jumpUrl>
"description": "<@safe_include "quote_response_description"/>",
"footer": {
<#assign adderUserName><@default_template_if_null adderName "quote_response_default_adder_name"/></#assign>
"text": "<@safe_include "quote_response_footer_adder_name" />"
<#if adderAvatarURL??>,"icon": "${adderAvatarURL}"</#if>
},
<#if imageAttachmentURLs?size = 1>
"imageUrl": "${imageAttachmentURLs[0]}",
<#assign userFound=authorUserDisplay?has_content>
<#assign authorName><#if authorMemberDisplay?has_content>${authorMemberDisplay.name}<#elseif authorUserDisplay?has_content>${authorUserDisplay.name}<#else><@safe_include "quote_response_default_author_name"/></#if></#assign>
<#assign adderUserName><#if adderMemberDisplay?has_content>${adderMemberDisplay.name}<#elseif adderUserDisplay?has_content>${adderUserDisplay.name}<#else><@safe_include "quote_response_default_adder_name"/></#if></#assign>
<#assign channelName><@default_template_if_null sourceChannelName "quote_response_default_channel_name"/></#assign>
<#assign creationDate><@format_instant_date_time instant=creationDate/></#assign>
<#if userFound>
<#assign authorAvatar><#if userFound>${authorUserDisplay.avatarUrl}</#if></#assign>
"type": "section",
"components": [
{
"type": "textDisplay",
"content": "<@safe_include "quote_response_header_author_name"/>"
}
],
"accessory": {
"type": "thumbnail",
"url": "${authorAvatar}"
}
<#else>
"type": "textDisplay",
"content": "<@safe_include "quote_response_header_author_name"/>"
</#if>
"timeStamp": "${creationDate}"
},
{
"type": "section",
"components": [
{
<#assign quoteId=quoteId>
"type": "textDisplay",
"content": "<@safe_include "quote_response_title"/>"
}
],
<#assign quoteJumpUrl=quotedMessage.jumpUrl>
"accessory": {
"type": "button",
"label": "<@safe_include "quote_response_jump_label"/>",
"url": "${quoteJumpUrl}",
"buttonStyle": "link"
}
},
{
"type": "container",
"components": [
<#assign hasContent=false>
<#if quoteContent?has_content>
<#assign hasContent=true>
{
"type": "textDisplay",
<#assign quoteDescription=quoteContent?json_string>
"content": "${quoteDescription}"
}
</#if>
<#if mediaAttachmentURLs?size gt 0>
<#assign hasContent=true>
,{
"type": "mediaGallery",
"images": [
<#list mediaAttachmentURLs as image>
{
"url": "${image}"
}<#sep>,</#list>
]
}
</#if>
<#if hasContent==false>
{
"type": "textDisplay",
"content": "<@safe_include "quote_response_no_content"/>"
}
</#if>
]
}
]
],
"messageConfig": {
"allowsUserMention": false,
"useComponentsV2": true
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>module-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>templates</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>template-overrides</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>template-overrides</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,5 +1,5 @@
{
<#assign userMention>${member.asMention}</#assign>
<#assign user>${member.user.name} (${member.asMention}: ${member.id})</#assign>
<#assign userText>${member.user.name} (${member.asMention}: ${member.id?c})</#assign>
"additionalMessage": "<@safe_include "user_joined_text"/>"
}

View File

@@ -1,5 +1,5 @@
{
<#include "user_detail">
<#assign user><@user_detail user=user/></#assign>
<#assign userText>${user.name} (${user.userMention}: ${user.id?c})</#assign>
"additionalMessage": "<@safe_include "user_left_text"/>"
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>template-overrides</artifactId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>sissi-templates</artifactId>
<groupId>dev.sheldan.sissi.templates</groupId>
<version>1.5.3-SNAPSHOT</version>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -13,6 +13,7 @@
<module>moderation-template-overrides</module>
<module>experience-template-overrides</module>
<module>logging-template-overrides</module>
<module>starboard-template-overrides</module>
</modules>
</project>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>dev.sheldan.sissi.templates</groupId>
<artifactId>template-overrides</artifactId>
<version>1.5.16</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>starboard-template-overrides</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>moderation-template-overrides-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

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