Compare commits

...

61 Commits

Author SHA1 Message Date
Sheldan
d5c8509ab0 [maven-release-plugin] prepare release oneplusbot-1.5.7 2021-09-06 02:21:12 +02:00
Sheldan
eaddea63b3 [OPB-xxx] preparing for release 2021-09-06 02:15:38 +02:00
Sheldan
7dc141a8a4 [OPB-xxx] adding logging dashboard to grafana 2021-09-06 01:34:23 +02:00
Sheldan
6652c90edb [OPB-37] fixing loki data source in docker compose config 2021-08-22 19:09:28 +02:00
Sheldan
d7f5fc21d6 [OPB-38] fixing json validation 2021-08-22 18:39:41 +02:00
Sheldan
bf98d5db3a [maven-release-plugin] prepare for next development iteration 2021-08-10 00:21:38 +02:00
Sheldan
57fb34920d [maven-release-plugin] prepare release oneplusbot-1.5.6 2021-08-10 00:21:31 +02:00
Sheldan
3f959f3bbb [OPB-37] adding loki log collector
upgrading to newer abstracto version
adding some info to readme
2021-08-09 23:42:23 +02:00
Sheldan
0380720ec8 [maven-release-plugin] prepare for next development iteration 2021-07-26 02:08:06 +02:00
Sheldan
5bea74cb22 [maven-release-plugin] prepare release oneplusbot-1.5.5 2021-07-26 02:07:58 +02:00
Sheldan
1b970ba0a8 [OPB-xxx] updating to hotfix and preparing for release 2021-07-26 01:46:13 +02:00
Sheldan
a707b10cfd [maven-release-plugin] prepare for next development iteration 2021-07-25 20:58:31 +02:00
Sheldan
124d29769e [maven-release-plugin] prepare release oneplusbot-1.5.4 2021-07-25 20:58:23 +02:00
Sheldan
d1aa2d5276 [OPB-xxx] preparing for release 2021-07-25 20:37:33 +02:00
Sheldan
4d569d4ec0 [OPB-32] fixing always exporting user ID in faq config when in reality it was bot ID
sorting faq commands and channel groups in export response alphabetically
2021-07-25 16:31:27 +02:00
Sheldan
81e58c3f7b [OPB-36] adding oneplus bot stats dashboard
restructuring oneplus bot status dashboard to include jda events and remove less relevant metrics
2021-07-25 15:25:30 +02:00
Sheldan
c04041cb35 [maven-release-plugin] prepare for next development iteration 2021-07-21 23:59:17 +02:00
Sheldan
37e566a957 [maven-release-plugin] prepare release oneplusbot-1.5.3 2021-07-21 23:59:09 +02:00
Sheldan
f7ca31a0c8 [OPB-xxx] update to newer abstracto version 2021-07-21 23:51:30 +02:00
Sheldan
8da31831b0 [maven-release-plugin] prepare for next development iteration 2021-07-14 02:40:28 +02:00
Sheldan
ed5b821da3 [maven-release-plugin] prepare release oneplusbot-1.5.2 2021-07-14 02:40:21 +02:00
Sheldan
cd875535d4 [OPB-30] prepare for release 2021-07-14 02:35:47 +02:00
Sheldan
8a38dd3d1a [OPB-30] porting mod mode command
enabling anti raid module
2021-07-14 02:09:29 +02:00
Sheldan
86eac5f2a5 [maven-release-plugin] prepare for next development iteration 2021-07-13 01:42:30 +02:00
Sheldan
8644af678f [maven-release-plugin] prepare release oneplusbot-1.5.1 2021-07-13 01:42:21 +02:00
Sheldan
6055a5e15f [OPB-xxx] updating abstracto version 2021-07-13 01:37:50 +02:00
Sheldan
aa71ad3f97 [OPB-34/OPB-27/OPB-35] enabling assignable role, scheduled activity and voice channel context
preparing for release
2021-07-13 00:48:18 +02:00
Sheldan
2985e46979 [OPB-31] adding privacy policy 2021-07-04 22:43:17 +02:00
Sheldan
93dfef5572 [maven-release-plugin] prepare for next development iteration 2021-07-04 14:05:42 +02:00
Sheldan
cab8cf1a8f [maven-release-plugin] prepare release oneplusbot-1.5.0 2021-07-04 14:05:36 +02:00
Sheldan
2066a2254d [OPB-28] preparing for release 2021-07-04 13:58:24 +02:00
Sheldan
75a5a1ff1a [OPB-28] enabling modmail
adding overrides for moderation colors
overriding ban notification text
2021-07-04 13:23:33 +02:00
Sheldan
97eb1761e3 [OPB-25] removing reaction from faq 2021-06-28 20:10:49 +02:00
Sheldan
cc3d4f38ab [OPB-xxx] apply new features regarding field limitations for urban definitions 2021-06-27 16:06:24 +02:00
Sheldan
c42b540bb6 [maven-release-plugin] prepare for next development iteration 2021-06-27 01:06:59 +02:00
Sheldan
3c56f9fb90 [maven-release-plugin] prepare release oneplusbot-1.4.4 2021-06-27 01:06:51 +02:00
Sheldan
f0ecd2643d [OPB-25] preparing for release 2021-06-27 00:37:04 +02:00
Sheldan
f943247e7d [OPB-25] implementation of FAQ commands 2021-06-27 00:16:53 +02:00
Sheldan
377b4ef478 [maven-release-plugin] prepare for next development iteration 2021-06-02 13:47:28 +02:00
Sheldan
a43e000d88 [maven-release-plugin] prepare release oneplusbot-1.4.3 2021-06-02 13:47:21 +02:00
Sheldan
ba7db6a542 [OPB-xx] updating to newer abstracto version 2021-06-02 13:01:01 +02:00
Sheldan
733d0ebd78 [maven-release-plugin] prepare for next development iteration 2021-06-02 01:06:07 +02:00
Sheldan
7e3fbe5ec6 [maven-release-plugin] prepare release oneplusbot-1.4.2 2021-06-02 01:06:00 +02:00
Sheldan
f304b20fe7 [OPB-26] enabling moderation module
updating versions
2021-06-01 21:49:03 +02:00
Sheldan
c9d1114731 [OPB-29] setting up logback configuration for requests 2021-06-01 21:46:01 +02:00
Sheldan
d8f37117c4 [maven-release-plugin] prepare for next development iteration 2021-05-26 22:12:54 +02:00
Sheldan
c1437373ff [maven-release-plugin] prepare release oneplusbot-1.4.1 2021-05-26 22:12:46 +02:00
Sheldan
1e2ab7fbfa [OPB-30] updating abstracto version and preparing for new release 2021-05-26 21:45:35 +02:00
Sheldan
b3b16d0e74 [maven-release-plugin] prepare for next development iteration 2021-05-26 15:10:35 +02:00
Sheldan
d3d79e8803 [maven-release-plugin] prepare release oneplusbot-1.4.0 2021-05-26 15:10:28 +02:00
Sheldan
bf448e167a Revert "[OPB-30] fixing abstracto version"
This reverts commit 1a135a9b
2021-05-26 14:18:38 +02:00
Sheldan
1a135a9bb8 [OPB-30] fixing abstracto version 2021-05-26 14:10:49 +02:00
Sheldan
fa5abaaed4 [OPB-30] enabling experience module
upgrading abstracto to version 1.2.13
preparing for release
2021-05-26 13:42:58 +02:00
Sheldan
2d98cbba81 [OPB-xxx] changing statistic color to be the op color 2021-05-24 22:37:04 +02:00
Sheldan
f7502c76de [maven-release-plugin] prepare for next development iteration 2021-05-24 00:02:56 +02:00
Sheldan
7c2b627fc9 [maven-release-plugin] prepare release oneplusbot-1.3.12 2021-05-24 00:02:50 +02:00
Sheldan
0419c70e49 [OPB-xxx] updating abstracto version 2021-05-23 23:39:08 +02:00
Sheldan
7777e4f1f0 [OPB-21] enabling emote tracking 2021-05-23 22:25:39 +02:00
Sheldan
02fd4af0f4 [OPB-20] enabling profanity filter 2021-05-23 18:01:32 +02:00
Sheldan
4778bca1ac [OPB-xxx] update readme 2021-05-12 01:13:52 +02:00
Sheldan
c14dc1a0a5 [maven-release-plugin] prepare for next development iteration 2021-05-12 01:12:14 +02:00
253 changed files with 15983 additions and 742 deletions

View File

@@ -34,5 +34,5 @@ jobs:
env:
REGISTRY_PREFIX: docker.pkg.github.com/sheldan/oneplusbot/
VERSION: ${{ env.version }}
ABSTRACTO_VERSION: 1.2.11
ABSTRACTO_VERSION: 1.3.8
ABSTRACTO_REGISTRY_PREFIX: docker.pkg.github.com/sheldan/abstracto/

148
PRIVACY_POLICY.md Normal file
View File

@@ -0,0 +1,148 @@
# r/oneplus Discord bot privacy policy
Last updated: 06.09.2021
## Description
The bot requires some information to function properly and in a reasonable way.
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**
* a logfile is used in order to examine any malfunctions, the content is deleted after 7 days
* 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
### Moderation
* the date of the latest report via the reaction report system, in order to disallow quick reports
* 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
* messages and users which were reported via reaction reports and how many times they were reported, but not *who* reported a message
* **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
### 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
### News
* general information about news posts
* the source message of the command, the created news post, and the author in order to enable the update mechanism
### Referral system
* the date of the latest referral post in order to enforce the referral bump mechanism
### 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
### Starboard
* 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 took
### 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
### FAQ
* the names of FAQ commands
* in which channel groups a FAQ command has a response
* the aliases of FAQ commands
* information about the actual FAQ message
* the **content** of the message
* the **URL** of the image used
* the color of the embed to be used
* the ID of the user to be used as author
* the amount of times a FAQ response has been used
### 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
## 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 an email to oneplus.appeals@pm.me with the subject: GDPR Data removal <Username#0000> <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._
## Legal information
The bot is not an official application from OnePlus or OPPO. You agree to use the bot at your own risk. The developers of the bot are not responsible for any damage done to your device, your computer or any other property.
We cannot promise you, as the user, to always update you when this Privacy Policy has changed. You can check the most recent version here. Important changes will be posted in the bot changelog channel of the r/oneplus Discord server.
OnePlus and OPPO are legal owners of OxygenOS. Visit https://oneplus.com/brand to view information about OnePlus.
## 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 a lot 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 [Docker Compose](https://github.com/docker/compose) is used 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

View File

@@ -1,11 +1,21 @@
# r/oneplus Discord server bot
This repository contains the rewrite of the [Bot](https://github.com/Rithari/OnePlusBot), which is still in use. The features will be gradually be ported
and most of them will be in [abstracto](https://github.com/Sheldan/abstracto), but some of the features need to be customized, as they are r/oneplus specific.
This repository contains the rewrite of the [Bot](https://github.com/Rithari/OnePlusBot). It is developed by customizing [abstracto](https://github.com/Sheldan/abstracto), because some features are r/oneplus specific.
The migration of the existing data from the database is handled via one time migration, and can be found [here](https://github.com/Sheldan/OnePlusBot-migration).
The FAQ configuration can be found [here](https://github.com/Sheldan/OnePlusBot-faq/).
Custom features which were ported
- [ ] FAQ
- [ ] Setup channel handling
- [ ] Referral link handling
- [x] FAQ
- [x] Setup channel handling
- [x] Referral link handling
# Technologies used in addition to the ones provided in abstracto
- [grafana](https://github.com/grafana/grafana) for visualization of the bot status and metrics
- [Loki](https://github.com/grafana/loki) to visualize and query log files
- [pgAdmin](https://github.com/postgres/pgadmin4) to view the database
- [prometheus](https://github.com/prometheus/prometheus) for metric collection
- [postgres](https://github.com/postgres/postgres) as a database
- [loki](https://github.com/grafana/loki) for log aggregation

View File

@@ -0,0 +1,34 @@
<?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.oneplus.bot.application</groupId>
<artifactId>application</artifactId>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>database</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

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

View File

@@ -0,0 +1,19 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="create_grafana_user">
<sql>
CREATE USER ${abstractografanadbuser} WITH PASSWORD '${abstractografanadbpass}';
GRANT CONNECT ON DATABASE abstracto TO ${abstractografanadbuser};
GRANT USAGE ON SCHEMA abstracto TO ${abstractografanadbuser};
GRANT SELECT ON ALL TABLES IN SCHEMA abstracto TO ${abstractografanadbuser};
ALTER DEFAULT PRIVILEGES IN SCHEMA abstracto
GRANT SELECT ON TABLES TO ${abstractografanadbuser};
</sql>
</changeSet>
</databaseChangeLog>

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.oneplus.bot.application</groupId>
<artifactId>application</artifactId>
<version>1.3.11</version>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>executable</artifactId>
@@ -100,22 +100,73 @@
<artifactId>starboard-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>modmail-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>assignable-roles-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>voice-channel-context-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>dynamic-activity-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>logging-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>anti-raid-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>invite-filter-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>statistic-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>profanity-filter-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>experience-tracking-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-impl</artifactId>
</dependency>
<dependency>
<groupId>dev.sheldan.oneplus.bot.application.custom</groupId>
<artifactId>starboard-custom</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.oneplus.bot.application.custom</groupId>
<artifactId>moderation-custom</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.oneplus.bot.application.modules</groupId>
<artifactId>news</artifactId>
@@ -134,6 +185,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.sheldan.oneplus.bot.application.modules</groupId>
<artifactId>faq</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -29,7 +29,26 @@
</appender>
<appender name="requests-file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/requests_log.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>
%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>
${LOG_PATH}/archived/requests_log_%d{dd-MM-yyyy}.log
</fileNamePattern>
<maxHistory>10</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<logger name="dev.sheldan.abstracto" level="INFO"/>
<logger name="dev.sheldan.abstracto.core.logging" level="DEBUG">
<appender-ref ref="requests-file"/>
</logger>
<root level="info">
<appender-ref ref="logFileAppender"/>

View File

@@ -0,0 +1,33 @@
<?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.oneplus.bot.application.custom</groupId>
<artifactId>oneplus-bot-customizations</artifactId>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dynamic-activity-custom</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

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

View File

@@ -0,0 +1,23 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:pro="http://www.liquibase.org/xml/ns/pro"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog dbchangelog.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext dbchangelog.xsd
http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" >
<changeSet author="Sheldan" id="dynamic_activity-insertion">
<insert tableName="activity">
<column name="type" value="STREAMING"/>
<column name="template_key" value="fans"/>
</insert>
<insert tableName="activity">
<column name="type" value="STREAMING"/>
<column name="template_key" value="help"/>
</insert>
<insert tableName="activity">
<column name="type" value="STREAMING"/>
<column name="template_key" value="modmail"/>
</insert>
</changeSet>
</databaseChangeLog>

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
<?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>
<artifactId>oneplus-bot-customizations</artifactId>
<groupId>dev.sheldan.oneplus.bot.application.custom</groupId>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>moderation-custom</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.sheldan.abstracto.modules</groupId>
<artifactId>moderation-int</artifactId>
<version>${abstracto.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,52 @@
package dev.sheldan.oneplus.bot.custom.moderation.commands;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.moderation.config.ModerationModuleDefinition;
import dev.sheldan.oneplus.bot.custom.moderation.config.ModerationCustomFeatureDefinition;
import dev.sheldan.oneplus.bot.custom.moderation.service.ModModeServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
@Component
public class ModMode extends AbstractConditionableCommand {
@Autowired
private ModModeServiceBean modModeServiceBean;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
Boolean newState = (Boolean) commandContext.getParameters().getParameters().get(0);
return modModeServiceBean.setModModeTo(commandContext.getGuild(), newState)
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter newStateParameter = Parameter.builder().name("newState").templated(true).type(Boolean.class).build();
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("modMode")
.async(true)
.module(ModerationModuleDefinition.MODERATION)
.parameters(Collections.singletonList(newStateParameter))
.help(helpInfo)
.templated(true)
.supportsEmbedException(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
}

View File

@@ -0,0 +1,35 @@
package dev.sheldan.oneplus.bot.custom.moderation.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.moderation.config.feature.ModerationFeatureConfig;
import dev.sheldan.oneplus.bot.custom.moderation.service.ModModeServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class ModerationCustomFeature implements FeatureConfig {
@Autowired
private ModerationFeatureConfig moderationFeatureConfig;
@Override
public FeatureDefinition getFeature() {
return ModerationCustomFeatureDefinition.MODERATION_CUSTOM;
}
@Override
public List<FeatureConfig> getRequiredFeatures() {
return Arrays.asList(moderationFeatureConfig);
}
@Override
public List<String> getRequiredSystemConfigKeys() {
return Arrays.asList(ModModeServiceBean.MODMODE_ROLE_CONFIG_KEY,
ModModeServiceBean.MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.custom.moderation.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum ModerationCustomFeatureDefinition implements FeatureDefinition {
MODERATION_CUSTOM("moderationCustom");
private String key;
ModerationCustomFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.oneplus.bot.custom.moderation.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:moderation-custom.properties")
public class ModerationCustomProperties {
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.custom.moderation.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class ModRoleNotFoundException extends AbstractoTemplatableException {
@Override
public String getTemplateName() {
return "mod_role_not_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.oneplus.bot.custom.moderation.service;
import dev.sheldan.abstracto.core.service.ConfigService;
import dev.sheldan.oneplus.bot.custom.moderation.exception.ModRoleNotFoundException;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ModModeServiceBean {
public static final String MODMODE_ROLE_CONFIG_KEY = "modModeRoleId";
public static final String MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY = "modModeNewRoleColor";
@Autowired
private ConfigService configService;
public CompletableFuture<Void> setModModeTo(Guild guild, Boolean newState) {
if(Boolean.TRUE.equals(newState)) {
return enableModMode(guild);
} else {
return disableModMoe(guild);
}
}
private CompletableFuture<Void> enableModMode(Guild guild) {
Color colorToSet = getColorFromConfig(MODMODE_CHANGED_ROLE_COLOR_CONFIG_KEY, guild);
return setModRoleTo(guild, colorToSet);
}
private CompletableFuture<Void> disableModMoe(Guild guild) {
return setModRoleTo(guild, null);
}
private Color getColorFromConfig(String key, Guild guild) {
String colorString = configService.getStringValueOrConfigDefault(key, guild.getIdLong());
String[] parts = colorString.split(",");
return new Color(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
}
private CompletableFuture<Void> setModRoleTo(Guild guild, Color color) {
Long roleId = configService.getLongValue(MODMODE_ROLE_CONFIG_KEY, guild.getIdLong());
Role modRole = guild.getRoleById(roleId);
if(modRole != null) {
return modRole.getManager().setColor(color).submit();
} else {
throw new ModRoleNotFoundException();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
abstracto.featureFlags.moderationCustom.featureName=moderationCustom
abstracto.featureFlags.moderationCustom.enabled=false
abstracto.systemConfigs.modModeRoleId.name=modModeRoleId
abstracto.systemConfigs.modModeRoleId.longValue=0
abstracto.systemConfigs.modModeNewRoleColor.name=modModeNewRoleColor
abstracto.systemConfigs.modModeNewRoleColor.stringValue=0,0,0

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.oneplus.bot.application</groupId>
<artifactId>application</artifactId>
<version>1.3.11</version>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -13,5 +13,7 @@
<modules>
<module>starboard-custom</module>
<module>dynamic-activity-custom</module>
<module>moderation-custom</module>
</modules>
</project>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>dev.sheldan.oneplus.bot.application.custom</groupId>
<artifactId>oneplus-bot-customizations</artifactId>
<version>1.3.11</version>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -0,0 +1,46 @@
<?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>
<artifactId>oneplus-bot-modules</artifactId>
<groupId>dev.sheldan.oneplus.bot.application.modules</groupId>
<version>1.5.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>faq</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/main/assembly/liquibase.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,18 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>liquibase</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<outputDirectory>.</outputDirectory>
<directory>${project.basedir}/src/main/resources/migrations</directory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,75 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.JSONValidationService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class DeleteFAQ extends AbstractConditionableCommand {
@Autowired
private FileService fileService;
@Autowired
private FAQServiceBean faqServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private JSONValidationService jsonValidationService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameter = commandContext.getParameters().getParameters();
String faqCommandName = (String) parameter.get(0);
AServer server = serverManagementService.loadServer(commandContext.getGuild());
faqServiceBean.deleteFAQCommand(faqCommandName, server);
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter fileAttachmentParameter = Parameter.builder().name("commandName").type(String.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(fileAttachmentParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("deleteFAQ")
.module(FAQModuleDefinition.FAQ)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
}

View File

@@ -0,0 +1,74 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ExportFAQ extends AbstractConditionableCommand {
@Autowired
private FAQServiceBean faqServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String configString;
AServer server = serverManagementService.loadServer(commandContext.getGuild().getIdLong());
if(parameters.isEmpty()) {
configString = faqServiceBean.exportFAQConfig(server);
} else {
String commandName = (String) parameters.get(0);
configString = faqServiceBean.exportFAQConfig(commandName, server);
}
return FutureUtils.toSingleFutureGeneric(channelService.sendFileToChannel(configString, "faqConfig.json", commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter commandNameParameter = Parameter.builder().name("commandName").type(String.class).optional(true).templated(true).build();
List<Parameter> parameters = Arrays.asList(commandNameParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("exportFAQ")
.module(FAQModuleDefinition.FAQ)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
}

View File

@@ -0,0 +1,114 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.models.command.faq.FAQResponseModel;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQResponseServiceBean;
import net.dv8tion.jda.api.entities.TextChannel;
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;
@Component
public class FAQ extends AbstractConditionableCommand {
@Autowired
private FAQResponseServiceBean faqService;
@Autowired
private ChannelService channelService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelManagementService channelManagementService;
private static final String FAQ_RESPONSE_TEMPLATE_KEY = "FAQ_response";
private static final String FAQ_RESPONSE_NO_COMMAND_FOUND_TEMPLATE_KEY = "FAQ_response_no_command_found";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
String commandName;
if(!parameters.isEmpty()) {
commandName = (String) parameters.get(0);
TextChannel channel;
if (parameters.size() == 2) {
channel = (TextChannel) parameters.get(1);
} else {
channel = commandContext.getChannel();
}
return faqService.loadFAQResponse(commandName, channel)
.thenCompose(faqResponseModel -> {
if (!faqResponseModel.getMessages().isEmpty()) {
List<CompletableFuture<Void>> messageFutures = new ArrayList<>();
faqResponseModel
.getMessages()
.forEach(faqResponseMessageModel ->
messageFutures.add(FutureUtils.
toSingleFutureGeneric(channelService.
sendEmbedTemplateInTextChannelList(FAQ_RESPONSE_TEMPLATE_KEY,
faqResponseMessageModel, commandContext.getChannel()))));
return FutureUtils.toSingleFutureGeneric(messageFutures);
} else {
return FutureUtils
.toSingleFutureGeneric(
channelService.
sendEmbedTemplateInTextChannelList(FAQ_RESPONSE_NO_COMMAND_FOUND_TEMPLATE_KEY,
faqResponseModel, commandContext.getChannel()));
}
})
.thenApply(unused -> CommandResult.fromSuccess());
} else {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
AChannel channel = channelManagementService.loadChannel(commandContext.getChannel());
FAQResponseModel model = faqService.getNoCommandFoundModel(server, channel);
return FutureUtils
.toSingleFutureGeneric(
channelService.
sendEmbedTemplateInTextChannelList(FAQ_RESPONSE_NO_COMMAND_FOUND_TEMPLATE_KEY,
model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter commandNameParameter = Parameter.builder().name("command").type(String.class).templated(true).optional(true).build();
Parameter channelParameter = Parameter.builder().name("channel").type(TextChannel.class).optional(true).templated(true).build();
List<Parameter> parameters = Arrays.asList(commandNameParameter, channelParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("FAQ")
.module(FAQModuleDefinition.FAQ)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
}

View File

@@ -0,0 +1,85 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
import dev.sheldan.abstracto.core.command.config.HelpInfo;
import dev.sheldan.abstracto.core.command.config.Parameter;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureMode;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.models.command.faquses.FAQUsageModel;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQUsageServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class FAQUsage extends AbstractConditionableCommand {
@Autowired
private FAQUsageServiceBean faqUsageServiceBean;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelService channelService;
private static final String FAQ_USAGE_RESPONSE_TEMPLATE_KEY = "FAQUsage_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameters = commandContext.getParameters().getParameters();
FAQUsageModel model;
AServer server = serverManagementService.loadServer(commandContext.getGuild());
if(parameters.isEmpty()) {
model = faqUsageServiceBean.getFAQUsageModel(server);
} else {
String commandName = (String) parameters.get(0);
model = faqUsageServiceBean.getFAQUsageModel(server, commandName);
}
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(FAQ_USAGE_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
Parameter commandNameParameter = Parameter.builder().name("commandName").type(String.class).templated(true).optional(true).build();
List<Parameter> parameters = Arrays.asList(commandNameParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("FAQUsage")
.module(FAQModuleDefinition.FAQ)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
@Override
public List<FeatureMode> getFeatureModeLimitations() {
return Arrays.asList(FAQFeatureMode.FAQ_USES);
}
}

View File

@@ -0,0 +1,112 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
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.exception.AbstractoTemplatedException;
import dev.sheldan.abstracto.core.command.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.JSONValidationResult;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.JSONValidationService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FileService;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.models.command.config.FaqCommandConfig;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQValidationServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.everit.json.schema.ValidationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Slf4j
@Component
public class ImportFAQ extends AbstractConditionableCommand {
@Autowired
private FileService fileService;
@Autowired
private FAQServiceBean faqServiceBean;
@Autowired
private FAQValidationServiceBean faqValidationServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private JSONValidationService jsonValidationService;
@Autowired
private ServerManagementService serverManagementService;
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
List<Object> parameter = commandContext.getParameters().getParameters();
File jsonConfigFile = (File) parameter.get(0);
try {
String jsonContent = FileUtils.readFileToString(jsonConfigFile, StandardCharsets.UTF_8);
JSONValidationResult result = faqValidationServiceBean.validateJSONForCreation(jsonContent);
if(result.getResult().equals(JSONValidationService.Result.SUCCESSFUL)) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
List<FaqCommandConfig> commands = faqServiceBean.loadFAQCommandsFromJson(jsonContent);
faqServiceBean.createOrUpdateFAQCommands(commands, server);
return CompletableFuture.completedFuture(CommandResult.fromSuccess());
} else {
List<String> errors = jsonValidationService.getDetailedException(result.getExceptions())
.stream()
.map(ValidationException::getMessage)
.collect(Collectors.toList());
channelService.sendTextToChannel(String.join("\n", errors), commandContext.getChannel());
return CompletableFuture.completedFuture(CommandResult.fromError("Incorrect faq config."));
}
} catch (IOException e) {
log.error("IO Exception when loading input file.", e);
throw new AbstractoTemplatedException("Failed to load json config.", "failed_to_set_template_exception", e);
} finally {
try {
fileService.safeDelete(jsonConfigFile);
} catch (IOException e) {
log.error("Failed to delete downloaded json file.", e);
}
}
}
@Override
public CommandConfiguration getConfiguration() {
Parameter fileAttachmentParameter = Parameter.builder().name("file").type(File.class).templated(true).build();
List<Parameter> parameters = Arrays.asList(fileAttachmentParameter);
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("importFAQ")
.module(FAQModuleDefinition.FAQ)
.parameters(parameters)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.causesReaction(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
}

View File

@@ -0,0 +1,63 @@
package dev.sheldan.oneplus.bot.modules.faq.command;
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.execution.CommandContext;
import dev.sheldan.abstracto.core.command.execution.CommandResult;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.FutureUtils;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQModuleDefinition;
import dev.sheldan.oneplus.bot.modules.faq.models.command.list.ListFAQCommandsModel;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component
@Slf4j
public class ListFAQCommands extends AbstractConditionableCommand {
@Autowired
private FAQServiceBean faqServiceBean;
@Autowired
private ChannelService channelService;
@Autowired
private ServerManagementService serverManagementService;
private static final String LIST_FAQ_COMMANDS_RESPONSE_TEMPLATE_KEY = "listFAQCommands_response";
@Override
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
AServer server = serverManagementService.loadServer(commandContext.getGuild());
ListFAQCommandsModel model = faqServiceBean.getCommandListingForServer(server);
return FutureUtils.toSingleFutureGeneric(channelService.sendEmbedTemplateInTextChannelList(LIST_FAQ_COMMANDS_RESPONSE_TEMPLATE_KEY, model, commandContext.getChannel()))
.thenApply(unused -> CommandResult.fromSuccess());
}
@Override
public CommandConfiguration getConfiguration() {
HelpInfo helpInfo = HelpInfo.builder().templated(true).build();
return CommandConfiguration.builder()
.name("listFAQCommands")
.module(FAQModuleDefinition.FAQ)
.supportsEmbedException(true)
.help(helpInfo)
.async(true)
.templated(true)
.build();
}
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
}

View File

@@ -0,0 +1,34 @@
package dev.sheldan.oneplus.bot.modules.faq.config;
import dev.sheldan.abstracto.core.config.FeatureConfig;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import dev.sheldan.abstracto.core.config.FeatureMode;
import dev.sheldan.abstracto.core.interactive.AutoDelayedAction;
import dev.sheldan.oneplus.bot.modules.faq.config.setup.GlobalChannelGroupDelayedActionConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class FAQFeatureConfig implements FeatureConfig {
@Autowired
private GlobalChannelGroupDelayedActionConfig globalChannelGroupDelayedActionConfig;
@Override
public FeatureDefinition getFeature() {
return FAQFeatureDefinition.FAQ;
}
@Override
public List<AutoDelayedAction> getAutoSetupSteps() {
return Arrays.asList(globalChannelGroupDelayedActionConfig);
}
@Override
public List<FeatureMode> getAvailableModes() {
return Arrays.asList(FAQFeatureMode.FAQ_USES);
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.faq.config;
import dev.sheldan.abstracto.core.config.FeatureDefinition;
import lombok.Getter;
@Getter
public enum FAQFeatureDefinition implements FeatureDefinition {
FAQ("faq");
private String key;
FAQFeatureDefinition(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.faq.config;
import dev.sheldan.abstracto.core.config.FeatureMode;
import lombok.Getter;
@Getter
public enum FAQFeatureMode implements FeatureMode {
FAQ_USES("faqUses");
private final String key;
FAQFeatureMode(String key) {
this.key = key;
}
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.oneplus.bot.modules.faq.config;
import dev.sheldan.abstracto.core.command.config.ModuleDefinition;
import dev.sheldan.abstracto.core.command.config.ModuleInfo;
import org.springframework.stereotype.Component;
@Component
public class FAQModuleDefinition implements ModuleDefinition {
public static final String FAQ = "faqModule";
@Override
public ModuleInfo getInfo() {
return ModuleInfo.builder().name(FAQ).templated(true).build();
}
@Override
public String getParentModule() {
return "default";
}
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.oneplus.bot.modules.faq.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:faq.properties")
public class FAQProperties {
}

View File

@@ -0,0 +1,24 @@
package dev.sheldan.oneplus.bot.modules.faq.config.setup;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class GlobalChannelGroupDelayActionConfig implements DelayedActionConfig {
private Long serverId;
@Override
public String getTemplateName() {
return "setup_global_channel_group_info";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.oneplus.bot.modules.faq.config.setup;
import dev.sheldan.abstracto.core.interactive.AutoDelayedAction;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.models.AServerChannelUserId;
import org.springframework.stereotype.Component;
@Component
public class GlobalChannelGroupDelayedActionConfig implements AutoDelayedAction {
@Override
public DelayedActionConfig getDelayedActionConfig(AServerChannelUserId aServerChannelUserId) {
return GlobalChannelGroupDelayActionConfig
.builder()
.serverId(aServerChannelUserId.getGuildId())
.build();
}
}

View File

@@ -0,0 +1,49 @@
package dev.sheldan.oneplus.bot.modules.faq.config.setup;
import dev.sheldan.abstracto.core.interactive.DelayedAction;
import dev.sheldan.abstracto.core.interactive.DelayedActionConfig;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.models.database.ChannelGroupType;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.abstracto.core.service.management.ChannelGroupTypeManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import static dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean.FAQ_CHANNEL_GROUP_KEY;
@Component
@Slf4j
public class GlobalChannelGroupSetupDelayedAction implements DelayedAction {
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelGroupTypeManagementService channelGroupTypeManagementService;
@Value("${abstracto.faq.globalChannelGroupName}")
private String globalChannelGroupName;
@Override
public void execute(DelayedActionConfig delayedActionConfig) {
GlobalChannelGroupDelayActionConfig config = (GlobalChannelGroupDelayActionConfig) delayedActionConfig;
AServer server = serverManagementService.loadServer(config.getServerId());
if(!channelGroupManagementService.doesChannelGroupExist(globalChannelGroupName, server)) {
ChannelGroupType faqChannelGroupType = channelGroupTypeManagementService.findChannelGroupTypeByKey(FAQ_CHANNEL_GROUP_KEY);
channelGroupManagementService.createChannelGroup(globalChannelGroupName, server, faqChannelGroupType);
} else {
log.info("Global channel group named {} already exists for server {}.", globalChannelGroupName, config.getServerId());
}
}
@Override
public boolean handles(DelayedActionConfig delayedActionConfig) {
return delayedActionConfig instanceof GlobalChannelGroupDelayActionConfig;
}
}

View File

@@ -0,0 +1,97 @@
package dev.sheldan.oneplus.bot.modules.faq.converter;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.service.BotService;
import dev.sheldan.oneplus.bot.modules.faq.models.command.config.*;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroupCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class FAQCommandConfigConverter {
@Autowired
private Gson gson;
@Autowired
private BotService botService;
public String serializeCommands(List<FAQCommand> asList) {
Long botId = botService.getInstance().getSelfUser().getIdLong();
asList.sort(Comparator.comparing(FAQCommand::getName));
List<FaqCommandConfig> config = asList.stream().map(faqCommand -> {
List<FAQChannelGroupCommand> responses = faqCommand.getGroupResponses();
responses.sort(Comparator.comparing(groupCommand -> groupCommand.getChannelGroup().getChannelGroup().getGroupName()));
List<FaqCommandResponseConfig> commandResponseConfigs = convertGroupCommands(responses, botId);
List<String> aliases;
if(faqCommand.getAliases() != null) {
aliases = faqCommand.getAliases().stream().map(faqCommandAlias -> faqCommandAlias.getId().getAlias()).collect(Collectors.toList());
} else {
aliases = new ArrayList<>();
}
return FaqCommandConfig
.builder()
.faqCommandName(faqCommand.getName())
.global(faqCommand.getGlobal())
.responses(commandResponseConfigs)
.aliases(aliases)
.build();
}).collect(Collectors.toList());
return gson.toJson(config);
}
private List<FaqCommandResponseConfig> convertGroupCommands(List<FAQChannelGroupCommand> responses, Long botUserId) {
return responses.stream().map(faqChannelGroupCommand -> {
List<FaqCommandResponseMessageConfig> responseConfigs = convertCommandResponses(faqChannelGroupCommand.getResponses(), botUserId);
return FaqCommandResponseConfig
.builder()
.channelGroupName(faqChannelGroupCommand.getChannelGroup().getChannelGroup().getGroupName())
.messages(responseConfigs)
.build();
}).collect(Collectors.toList());
}
private List<FaqCommandResponseMessageConfig> convertCommandResponses(List<FAQCommandResponse> responses, Long botUserId) {
return responses.stream().map(response -> convertCommandResponse(response, botUserId)).collect(Collectors.toList());
}
private FaqCommandResponseMessageConfig convertCommandResponse(FAQCommandResponse response, Long botUserId) {
FaqCommandResponseEmbedConfig embedConfig = null;
if(response.getDescription() != null) {
boolean useBot = response.getAuthorUserId().equals(botUserId);
FaqCommandResponseEmbedColorConfig colorConfig = FaqCommandResponseEmbedColorConfig
.builder()
.red(response.getRed())
.green(response.getGreen())
.blue(response.getBlue())
.build();
FaqCommandResponseEmbedAuthorConfig authorConfig = FaqCommandResponseEmbedAuthorConfig
.builder()
.userId(useBot ? null : response.getAuthorUserId())
.useBot(useBot)
.build();
embedConfig = FaqCommandResponseEmbedConfig
.builder()
.description(response.getDescription())
.imageUrl(response.getImageURL())
.color(colorConfig)
.author(authorConfig)
.build();
}
return FaqCommandResponseMessageConfig
.builder()
.position(response.getId().getPosition())
.additionalMessage(response.getAdditionalMessage())
.embed(embedConfig)
.build();
}
}

View File

@@ -0,0 +1,58 @@
package dev.sheldan.oneplus.bot.modules.faq.converter;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.oneplus.bot.modules.faq.models.command.list.ListFAQCommandsCommandChannelGroupModel;
import dev.sheldan.oneplus.bot.modules.faq.models.command.list.ListFAQCommandsModel;
import dev.sheldan.oneplus.bot.modules.faq.models.command.list.ListFAQCommandsCommandModel;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroupCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommand;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class ListFAQCommandsModelConverter {
public ListFAQCommandsModel fromFAQCommands(List<FAQCommand> faqCommandList) {
List<ListFAQCommandsCommandModel> commands = faqCommandList
.stream()
.map(this::convertCommand)
.collect(Collectors.toList());
return ListFAQCommandsModel
.builder()
.commands(commands)
.build();
}
private ListFAQCommandsCommandModel convertCommand(FAQCommand faqCommand) {
List<ListFAQCommandsCommandChannelGroupModel> channelGroups = faqCommand
.getGroupResponses()
.stream()
.map(this::convertGroupCommand)
.collect(Collectors.toList());
List<String> aliases;
if(faqCommand.getAliases() != null) {
aliases = faqCommand.getAliases().stream().map(faqCommandAlias -> faqCommandAlias.getId().getAlias()).collect(Collectors.toList());
} else {
aliases = new ArrayList<>();
}
return ListFAQCommandsCommandModel
.builder()
.commandName(faqCommand.getName())
.aliases(aliases)
.channelGroups(channelGroups)
.build();
}
private ListFAQCommandsCommandChannelGroupModel convertGroupCommand(FAQChannelGroupCommand faqChannelGroupCommand) {
AChannelGroup channelGroup = faqChannelGroupCommand.getChannelGroup().getChannelGroup();
return ListFAQCommandsCommandChannelGroupModel
.builder()
.responseCount(faqChannelGroupCommand.getResponses().size())
.channelGroupName(channelGroup.getGroupName())
.build();
}
}

View File

@@ -0,0 +1,29 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.DuplicatedCommandNameExceptionModel;
import java.util.List;
public class DuplicatedCommandNameOrAliasException extends AbstractoTemplatableException {
private final DuplicatedCommandNameExceptionModel model;
public DuplicatedCommandNameOrAliasException(List<String> commandKeys) {
super("Duplicated command name or alias found.");
this.model = DuplicatedCommandNameExceptionModel
.builder()
.duplicatedCommandKeys(commandKeys)
.build();
}
@Override
public String getTemplateName() {
return "duplicated_command_or_alias_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.DuplicatedFAQCommandAliasExceptionModel;
public class DuplicatedFAQCommandAliasException extends AbstractoTemplatableException {
private final DuplicatedFAQCommandAliasExceptionModel model;
public DuplicatedFAQCommandAliasException(String commandName, String alias, String usedCommand) {
super("Alias for FAQ command is already used as an alias in another name.");
this.model = DuplicatedFAQCommandAliasExceptionModel
.builder()
.alias(alias)
.commandName(commandName)
.originCommandName(usedCommand)
.build();
}
@Override
public String getTemplateName() {
return "duplicated_faq_command_alias_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.FAQCommandAliasShadowingExceptionModel;
public class FAQCommandAliasShadowingException extends AbstractoTemplatableException {
private final FAQCommandAliasShadowingExceptionModel model;
public FAQCommandAliasShadowingException(String commandName, String alias) {
super("Alias shadows another existing command.");
this.model = FAQCommandAliasShadowingExceptionModel
.builder()
.alias(alias)
.commandName(commandName)
.build();
}
@Override
public String getTemplateName() {
return "faq_command_alias_shadowing_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.FAQCommandNotFoundExceptionModel;
public class FAQCommandNotFoundException extends AbstractoTemplatableException {
private final FAQCommandNotFoundExceptionModel model;
public FAQCommandNotFoundException(String commandName) {
super("FAQ command not found.");
this.model = FAQCommandNotFoundExceptionModel
.builder()
.commandName(commandName)
.build();
}
@Override
public String getTemplateName() {
return "faq_command_not_found_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,27 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.FAQCommandResponseDuplicatedPositionExceptionModel;
public class FAQCommandResponseDuplicatedPositionException extends AbstractoTemplatableException {
private final FAQCommandResponseDuplicatedPositionExceptionModel model;
public FAQCommandResponseDuplicatedPositionException(String commandName, String channelGroupName) {
super("Two messages within a FAQ command response have the same position.");
this.model = FAQCommandResponseDuplicatedPositionExceptionModel
.builder()
.channelGroupName(channelGroupName)
.commandName(commandName)
.build();
}
@Override
public String getTemplateName() {
return "faq_command_response_duplicated_position_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,26 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.GlobalFAQCommandConfigMismatchExceptionModel;
public class GlobalFAQCommandConfigMismatchException extends AbstractoTemplatableException {
private final GlobalFAQCommandConfigMismatchExceptionModel model;
public GlobalFAQCommandConfigMismatchException(String faqCommandName) {
super("The configuration for a global FAQ command does not make sense. It must be a the global FAQ channel group and the command needs to have global enabled.");
this.model = GlobalFAQCommandConfigMismatchExceptionModel
.builder()
.commandName(faqCommandName)
.build();
}
@Override
public String getTemplateName() {
return "global_faq_command_config_mismatch_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,28 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
import dev.sheldan.oneplus.bot.modules.faq.models.exception.GlobalCommandResponseExceptionModel;
import java.util.List;
public class GlobalFAQCommandResponsesException extends AbstractoTemplatableException {
private final GlobalCommandResponseExceptionModel model;
public GlobalFAQCommandResponsesException(List<String> commandNames) {
super("A global command can only have one response.");
this.model = GlobalCommandResponseExceptionModel
.builder()
.commandNames(commandNames)
.build();
}
@Override
public String getTemplateName() {
return "global_faq_command_responses_exception";
}
@Override
public Object getTemplateModel() {
return this.model;
}
}

View File

@@ -0,0 +1,20 @@
package dev.sheldan.oneplus.bot.modules.faq.exception;
import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException;
public class NoFAQResponseFoundException extends AbstractoTemplatableException {
public NoFAQResponseFoundException() {
super("No FAQ Command response found.");
}
@Override
public String getTemplateName() {
return "no_faq_response_found_exception";
}
@Override
public Object getTemplateModel() {
return new Object();
}
}

View File

@@ -0,0 +1,49 @@
package dev.sheldan.oneplus.bot.modules.faq.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.entity.AsyncChannelGroupCreatedListener;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.listener.ChannelGroupCreatedListenerModel;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroup;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import dev.sheldan.oneplus.bot.modules.faq.service.management.FAQChannelGroupManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class FAQAsyncChannelGroupCreatedListener implements AsyncChannelGroupCreatedListener {
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Autowired
private FAQChannelGroupManagementServiceBean faqChannelGroupManagementServiceBean;
@Value("${abstracto.faq.globalChannelGroupName}")
private String globalChannelGroupName;
@Override
public DefaultListenerResult execute(ChannelGroupCreatedListenerModel model) {
AChannelGroup channelGroup = channelGroupManagementService.findChannelGroupById(model.getChannelGroupId());
if(channelGroup.getChannelGroupType().getGroupTypeKey().equals(FAQServiceBean.FAQ_CHANNEL_GROUP_KEY)) {
FAQChannelGroup createdChannelGroup = faqChannelGroupManagementServiceBean.createFAQChannelGroup(channelGroup);
if(channelGroup.getGroupName().equals(globalChannelGroupName)) {
Optional<FAQChannelGroup> globalGroup = faqChannelGroupManagementServiceBean.getGlobalChannelGroup(channelGroup.getServer());
globalGroup.ifPresent(faqChannelGroup -> {
log.info("Setting channel group {} to not be global for server {}.", faqChannelGroup.getId(), channelGroup.getServer().getId());
faqChannelGroup.setGlobal(false);
});
log.info("Setting channel group {} to be global for server {}.", channelGroup.getId(), channelGroup.getServer().getId());
createdChannelGroup.setGlobal(true);
}
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.oneplus.bot.modules.faq.listener;
import dev.sheldan.abstracto.core.listener.DefaultListenerResult;
import dev.sheldan.abstracto.core.listener.sync.entity.ChannelGroupDeletedListener;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.listener.ChannelGroupDeletedListenerModel;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.oneplus.bot.modules.faq.service.FAQServiceBean;
import dev.sheldan.oneplus.bot.modules.faq.service.management.FAQChannelGroupManagementServiceBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FAQChannelGroupDeletedListener implements ChannelGroupDeletedListener {
@Autowired
private FAQChannelGroupManagementServiceBean faqChannelGroupManagementServiceBean;
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Override
public DefaultListenerResult execute(ChannelGroupDeletedListenerModel model) {
AChannelGroup channelGroup = channelGroupManagementService.findChannelGroupById(model.getChannelGroupId());
if(channelGroup.getChannelGroupType().getGroupTypeKey().equals(FAQServiceBean.FAQ_CHANNEL_GROUP_KEY)) {
faqChannelGroupManagementServiceBean.deleteFAQChannelGroup(channelGroup);
return DefaultListenerResult.PROCESSED;
}
return DefaultListenerResult.IGNORED;
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
import java.util.List;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandConfig {
private String faqCommandName;
private Boolean global;
private List<String> aliases;
private List<FaqCommandResponseConfig> responses;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
import java.util.List;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandResponseConfig {
private String channelGroupName;
private List<FaqCommandResponseMessageConfig> messages;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandResponseEmbedAuthorConfig {
private Long userId;
private Boolean useBot;
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandResponseEmbedColorConfig {
@Builder.Default
private Integer red = 0;
@Builder.Default
private Integer green = 0;
@Builder.Default
private Integer blue = 0;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandResponseEmbedConfig {
private String description;
private String imageUrl;
private FaqCommandResponseEmbedColorConfig color;
private FaqCommandResponseEmbedAuthorConfig author;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.config;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FaqCommandResponseMessageConfig {
private Integer position;
private String additionalMessage;
private FaqCommandResponseEmbedConfig embed;
}

View File

@@ -0,0 +1,33 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.faq;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandResponse;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import net.dv8tion.jda.api.entities.User;
@Getter
@Setter
@Builder
public class FAQResponseMessageModel {
private User author;
private String additionalMessage;
private String description;
private String imageURL;
private Integer red;
private Integer green;
private Integer blue;
public static FAQResponseMessageModel fromFAQCommandResponse(FAQCommandResponse response, User user) {
return FAQResponseMessageModel
.builder()
.author(user)
.additionalMessage(response.getAdditionalMessage())
.description(response.getDescription())
.blue(response.getBlue())
.green(response.getGreen())
.red(response.getRed())
.imageURL(response.getImageURL())
.build();
}
}

View File

@@ -0,0 +1,18 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.faq;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Builder
public class FAQResponseModel {
@Builder.Default
private List<FAQResponseMessageModel> messages = new ArrayList<>();
@Builder.Default
private List<String> availableCommands = new ArrayList<>();
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.faquses;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class FAQCommandResponseUsage {
private String channelGroupName;
private Integer uses;
}

View File

@@ -0,0 +1,15 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.faquses;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class FAQCommandUsage {
private String faqCommandName;
private List<FAQCommandResponseUsage> faqChannelGroupUsages;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.faquses;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class FAQUsageModel {
private List<FAQCommandUsage> uses;
}

View File

@@ -0,0 +1,11 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.list;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class ListFAQCommandsCommandChannelGroupModel {
private String channelGroupName;
private Integer responseCount;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.list;
import lombok.Builder;
import lombok.Getter;
import java.util.List;
@Builder
@Getter
public class ListFAQCommandsCommandModel {
private String commandName;
private List<String> aliases;
private List<ListFAQCommandsCommandChannelGroupModel> channelGroups;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.faq.models.command.list;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Builder
@Getter
public class ListFAQCommandsModel {
private List<ListFAQCommandsCommandModel> commands;
}

View File

@@ -0,0 +1,34 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "faq_channel_group")
@Getter
@Setter
@EqualsAndHashCode
public class FAQChannelGroup {
@Id
@Column(name = "id")
private Long id;
@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@PrimaryKeyJoinColumn
private AChannelGroup channelGroup;
@Column(name = "global")
private Boolean global;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -0,0 +1,44 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.ChannelGroupCommandId;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "faq_channel_group_command")
@EqualsAndHashCode
@Getter
@Setter
public class FAQChannelGroupCommand implements Serializable {
@EmbeddedId
private ChannelGroupCommandId groupCommandId;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("channelGroupId")
@JoinColumn(name = "channel_group_id", referencedColumnName = "id", nullable = false)
private FAQChannelGroup channelGroup;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("commandId")
@JoinColumn(name = "command_id", referencedColumnName = "id", nullable = false)
private FAQCommand command;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "groupCommand")
@Builder.Default
private List<FAQCommandResponse> responses = new ArrayList<>();
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "uses")
private Integer uses;
}

View File

@@ -0,0 +1,52 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database;
import dev.sheldan.abstracto.core.models.database.AServer;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "faq_command")
@Getter
@Setter
@EqualsAndHashCode
public class FAQCommand implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name", length = 20)
private String name;
@Column(name = "global")
private Boolean global;
@Getter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", nullable = false)
private AServer server;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
@Getter
@OneToMany(fetch = FetchType.LAZY, mappedBy = "command")
@Builder.Default
private List<FAQChannelGroupCommand> groupResponses = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY, mappedBy = "command")
@Builder.Default
private List<FAQCommandAlias> aliases = new ArrayList<>();
}

View File

@@ -0,0 +1,31 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.FAQCommandAliasId;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "faq_command_alias")
@Getter
@Setter
@EqualsAndHashCode
public class FAQCommandAlias implements Serializable {
@EmbeddedId
private FAQCommandAliasId id;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("commandId")
@JoinColumn(name = "command_id", referencedColumnName = "id", nullable = false)
private FAQCommand command;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
}

View File

@@ -0,0 +1,55 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.CommandResponseId;
import lombok.*;
import javax.persistence.*;
import java.time.Instant;
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "faq_command_response")
@Getter
@Setter
@EqualsAndHashCode
public class FAQCommandResponse {
@EmbeddedId
private CommandResponseId id;
@Column(name = "description", length = 2000)
private String description;
@Column(name = "additional_message", length = 2000)
private String additionalMessage;
@Column(name = "image_url", length = 2000)
private String imageURL;
@Column(name = "red")
private Integer red;
@Column(name = "green")
private Integer green;
@Column(name = "blue")
private Integer blue;
@Column(name = "author_user_id")
private Long authorUserId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns(
{
@JoinColumn(updatable = false, insertable = false, name = "channel_group_id", referencedColumnName = "channel_group_id"),
@JoinColumn(updatable = false, insertable = false, name = "command_id", referencedColumnName = "command_id")
})
private FAQChannelGroupCommand groupCommand;
@Column(name = "created", nullable = false, insertable = false, updatable = false)
private Instant created;
@Column(name = "updated", insertable = false, updatable = false)
private Instant updated;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database.embed;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class ChannelGroupCommandId implements Serializable {
@Column(name = "channel_group_id")
private Long channelGroupId;
@Column(name = "command_id")
private Long commandId;
}

View File

@@ -0,0 +1,23 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database.embed;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@EqualsAndHashCode
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommandResponseId implements Serializable {
@Column(name = "channel_group_id")
private Long channelGroupId;
@Column(name = "command_id")
private Long commandId;
@Column(name = "position")
private Integer position;
}

View File

@@ -0,0 +1,21 @@
package dev.sheldan.oneplus.bot.modules.faq.models.database.embed;
import lombok.*;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Getter
@Setter
@EqualsAndHashCode
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FAQCommandAliasId implements Serializable {
@Column(name = "command_id")
private Long faqCommandId;
@Column(name = "alias", length = 20)
private String alias;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class DuplicatedCommandNameExceptionModel {
private List<String> duplicatedCommandKeys;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class DuplicatedFAQCommandAliasExceptionModel {
private String commandName;
private String alias;
private String originCommandName;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class FAQCommandAliasShadowingExceptionModel {
private String commandName;
private String alias;
}

View File

@@ -0,0 +1,12 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class FAQCommandNotFoundExceptionModel {
private String commandName;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class FAQCommandResponseDuplicatedPositionExceptionModel {
private String commandName;
private String channelGroupName;
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@Builder
public class GlobalCommandResponseExceptionModel {
private List<String> commandNames;
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.oneplus.bot.modules.faq.models.exception;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class GlobalFAQCommandConfigMismatchExceptionModel {
private String commandName;
}

View File

@@ -0,0 +1,16 @@
package dev.sheldan.oneplus.bot.modules.faq.repository;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroup;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroupCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.ChannelGroupCommandId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface FAQChannelGroupCommandRepository extends JpaRepository<FAQChannelGroupCommand, ChannelGroupCommandId> {
List<FAQChannelGroupCommand> findByChannelGroup(FAQChannelGroup faqChannelGroup);
List<FAQChannelGroupCommand> findByChannelGroup_IdIn(List<Long> channelGroupIds);
List<FAQChannelGroupCommand> findByChannelGroupIn(List<FAQChannelGroup> faqChannelGroup);
}

View File

@@ -0,0 +1,13 @@
package dev.sheldan.oneplus.bot.modules.faq.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroup;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface FAQChannelGroupRepository extends JpaRepository<FAQChannelGroup, Long> {
Optional<FAQChannelGroup> findByChannelGroup_ServerAndGlobalTrue(AServer server);
}

View File

@@ -0,0 +1,14 @@
package dev.sheldan.oneplus.bot.modules.faq.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandAlias;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.FAQCommandAliasId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface FAQCommandAliasRepository extends JpaRepository<FAQCommandAlias, FAQCommandAliasId> {
Optional<FAQCommandAlias> findById_AliasAndCommand_Server(String alias, AServer server);
}

View File

@@ -0,0 +1,17 @@
package dev.sheldan.oneplus.bot.modules.faq.repository;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommand;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface FAQCommandRepository extends JpaRepository<FAQCommand, Long> {
Optional<FAQCommand> findByNameEqualsIgnoreCaseAndServer(String name, AServer server);
List<FAQCommand> findByNameInIgnoreCaseAndServer(List<String> names, AServer server);
List<FAQCommand> findByServer(AServer server);
List<FAQCommand> findByServerAndGlobalTrue(AServer server);
}

View File

@@ -0,0 +1,10 @@
package dev.sheldan.oneplus.bot.modules.faq.repository;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandResponse;
import dev.sheldan.oneplus.bot.modules.faq.models.database.embed.CommandResponseId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface FAQCommandResponseRepository extends JpaRepository<FAQCommandResponse, CommandResponseId> {
}

View File

@@ -0,0 +1,175 @@
package dev.sheldan.oneplus.bot.modules.faq.service;
import dev.sheldan.abstracto.core.models.database.AChannel;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.ChannelGroupService;
import dev.sheldan.abstracto.core.service.FeatureModeService;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.ChannelManagementService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import dev.sheldan.abstracto.core.utils.CompletableFutureList;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureDefinition;
import dev.sheldan.oneplus.bot.modules.faq.config.FAQFeatureMode;
import dev.sheldan.oneplus.bot.modules.faq.exception.NoFAQResponseFoundException;
import dev.sheldan.oneplus.bot.modules.faq.models.command.faq.FAQResponseMessageModel;
import dev.sheldan.oneplus.bot.modules.faq.models.command.faq.FAQResponseModel;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroupCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandAlias;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandResponse;
import dev.sheldan.oneplus.bot.modules.faq.service.management.FAQChannelGroupCommandManagementServiceBean;
import dev.sheldan.oneplus.bot.modules.faq.service.management.FAQCommandAliasManagementService;
import dev.sheldan.oneplus.bot.modules.faq.service.management.FAQCommandManagementServiceBean;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.ISnowflake;
import net.dv8tion.jda.api.entities.SelfUser;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Slf4j
public class FAQResponseServiceBean {
@Autowired
private ServerManagementService serverManagementService;
@Autowired
private ChannelGroupService channelGroupService;
@Autowired
private FAQCommandManagementServiceBean faqCommandManagementServiceBean;
@Autowired
private FAQChannelGroupCommandManagementServiceBean faqChannelGroupCommandManagementServiceBean;
@Autowired
private FAQCommandAliasManagementService faqCommandAliasManagementService;
@Autowired
private ChannelManagementService channelManagementService;
@Autowired
private UserService userService;
@Autowired
private FeatureModeService featureModeService;
@Autowired
private FAQResponseServiceBean self;
public CompletableFuture<FAQResponseModel> loadFAQResponse(String commandName, TextChannel textChannel) {
AServer server = serverManagementService.loadServer(textChannel.getGuild().getIdLong());
Optional<FAQCommand> faqCommandOptional = faqCommandManagementServiceBean.findByNameAndServer(commandName, server);
if(!faqCommandOptional.isPresent()) {
log.debug("Did not find the command, trying via command alias.");
Optional<FAQCommandAlias> faqCommandAlias = faqCommandAliasManagementService.findByNameAndServer(commandName, server);
if(faqCommandAlias.isPresent()) {
faqCommandOptional = Optional.of(faqCommandAlias.get().getCommand());
}
}
AChannel channel = channelManagementService.loadChannel(textChannel.getIdLong());
if (!faqCommandOptional.isPresent()) {
log.debug("Did not find a faq response. Returning model with possible command names.");
return CompletableFuture.completedFuture(getNoCommandFoundModel(server, channel));
} else {
FAQCommand command = faqCommandOptional.get();
Optional<FAQChannelGroupCommand> groupCommand;
if(command.getGlobal()) {
groupCommand = command.getGroupResponses().stream().findAny();
} else {
Stream<FAQChannelGroupCommand> matchingChannels = command
.getGroupResponses()
.stream()
.filter(faqChannelGroupCommand -> channelGroupService.isChannelInGroup(channel, faqChannelGroupCommand.getChannelGroup().getChannelGroup()));
List<FAQChannelGroupCommand> allMatchingCommands = matchingChannels.collect(Collectors.toList());
if(allMatchingCommands.size() > 1) {
log.warn("There are multiple matching channel groups for command {} and channel {}.", command.getId(), channel.getId());
}
if(!allMatchingCommands.isEmpty()) {
FAQChannelGroupCommand foundCommandChannelGroup = allMatchingCommands.get(0);
log.info("Using response from channel group {} for command {} in server {}.", foundCommandChannelGroup.getChannelGroup().getId(), command.getId(), server.getId());
groupCommand = Optional.of(foundCommandChannelGroup);
} else {
groupCommand = Optional.empty();
}
}
if(groupCommand.isPresent()) {
FAQChannelGroupCommand channelGroupCommand = groupCommand.get();
if(featureModeService.featureModeActive(FAQFeatureDefinition.FAQ, server.getId(), FAQFeatureMode.FAQ_USES)){
log.debug("Incrementing use counter for channel group {} for command {} in server {}.", channelGroupCommand.getChannelGroup().getId(), command.getId(), server.getId());
channelGroupCommand.setUses(channelGroupCommand.getUses() + 1);
}
Set<Long> userIds = channelGroupCommand
.getResponses()
.stream()
.map(FAQCommandResponse::getAuthorUserId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Long commandId = channelGroupCommand.getGroupCommandId().getCommandId();
Long channelGroupId = channelGroupCommand.getGroupCommandId().getChannelGroupId();
CompletableFuture<FAQResponseModel> modelFuture = new CompletableFuture<>();
if(userIds.isEmpty()) {
log.info("No user could not be found for channel group {} for command {} in server {}. Continuing with the bot as user.", channelGroupCommand.getChannelGroup().getId(), command.getId(), server.getId());
self.createModel(commandId, channelGroupId, null, modelFuture);
} else {
CompletableFutureList<User> userFutures = userService.retrieveUsers(new ArrayList<>(userIds));
userFutures
.getMainFuture()
.whenComplete((unused, throwable) -> self.createModel(commandId, channelGroupId, userFutures, modelFuture));
}
return modelFuture;
} else {
throw new NoFAQResponseFoundException();
}
}
}
public FAQResponseModel getNoCommandFoundModel(AServer server, AChannel channel) {
List<AChannelGroup> channelGroups = channelGroupService.getChannelGroupsOfChannelWithType(channel, FAQServiceBean.FAQ_CHANNEL_GROUP_KEY);
List<FAQChannelGroupCommand> possibleCommands = faqChannelGroupCommandManagementServiceBean.getGroupCommandsForBasicChannelGroups(channelGroups);
List<FAQCommand> globalCommands = faqCommandManagementServiceBean.findGlobalCommandsInServer(server);
Set<String> commandNames = possibleCommands
.stream()
.map(faqChannelGroupCommand -> faqChannelGroupCommand.getCommand().getName())
.collect(Collectors.toSet());
commandNames.addAll(globalCommands.stream().map(FAQCommand::getName).collect(Collectors.toSet()));
return FAQResponseModel
.builder()
.availableCommands(new ArrayList<>(commandNames))
.build();
}
@Transactional
public void createModel(Long commandId, Long channelGroupId, CompletableFutureList<User> userFutures, CompletableFuture<FAQResponseModel> modelFuture) {
try {
FAQChannelGroupCommand loadedGroupCommand = faqChannelGroupCommandManagementServiceBean.findChannelGroupCommand(commandId, channelGroupId);
FAQResponseModel mainModel = FAQResponseModel.builder().build();
SelfUser selfUser = userService.getSelfUser();
if(userFutures != null) {
List<User> users = userFutures.getObjects();
Map<Long, User> userMap = users.stream().collect(Collectors.toMap(ISnowflake::getIdLong, Function.identity()));
loadedGroupCommand.getResponses().forEach(response -> {
User user = userMap.getOrDefault(response.getAuthorUserId(), selfUser);
mainModel.getMessages().add(FAQResponseMessageModel.fromFAQCommandResponse(response, user));
});
} else {
loadedGroupCommand
.getResponses()
.forEach(response -> mainModel.getMessages().add(FAQResponseMessageModel.fromFAQCommandResponse(response, selfUser)));
}
modelFuture.complete(mainModel);
} catch (Exception exception) {
modelFuture.completeExceptionally(exception);
}
}
}

View File

@@ -0,0 +1,411 @@
package dev.sheldan.oneplus.bot.modules.faq.service;
import com.google.gson.Gson;
import dev.sheldan.abstracto.core.models.database.AChannelGroup;
import dev.sheldan.abstracto.core.models.database.AServer;
import dev.sheldan.abstracto.core.service.UserService;
import dev.sheldan.abstracto.core.service.management.ChannelGroupManagementService;
import dev.sheldan.oneplus.bot.modules.faq.converter.FAQCommandConfigConverter;
import dev.sheldan.oneplus.bot.modules.faq.converter.ListFAQCommandsModelConverter;
import dev.sheldan.oneplus.bot.modules.faq.exception.*;
import dev.sheldan.oneplus.bot.modules.faq.models.command.config.*;
import dev.sheldan.oneplus.bot.modules.faq.models.command.list.ListFAQCommandsModel;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroup;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQChannelGroupCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommand;
import dev.sheldan.oneplus.bot.modules.faq.models.database.FAQCommandResponse;
import dev.sheldan.oneplus.bot.modules.faq.service.management.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
@Slf4j
public class FAQServiceBean {
public static final String FAQ_CHANNEL_GROUP_KEY = "faq";
@Autowired
private ChannelGroupManagementService channelGroupManagementService;
@Autowired
private Gson gson;
@Autowired
private FAQCommandManagementServiceBean faqCommandManagementServiceBean;
@Autowired
private FAQChannelGroupManagementServiceBean faqChannelGroupManagementServiceBean;
@Autowired
private FAQCommandResponseManagementServiceBean faqCommandResponseManagementServiceBean;
@Autowired
private FAQChannelGroupCommandManagementServiceBean faqChannelGroupCommandManagementServiceBean;
@Autowired
private FAQCommandAliasManagementService faqCommandAliasManagementService;
@Autowired
private UserService userService;
@Autowired
private FAQCommandConfigConverter configConverter;
@Autowired
private ListFAQCommandsModelConverter listFAQCommandsModelConverter;
public void createOrUpdateFAQCommands(List<FaqCommandConfig> commands, AServer server) {
fillDefaults(commands);
validateCommandConfig(commands, server);
commands.forEach(faqCommandConfig -> handleFAQCommand(faqCommandConfig, server));
}
private void validateCommandConfig(List<FaqCommandConfig> commands, AServer server) {
List<FaqCommandConfig> incorrectGlobalConfigurations = commands
.stream()
.filter(faqCommandConfig -> faqCommandConfig.getGlobal() && faqCommandConfig.getResponses().size() > 1)
.collect(Collectors.toList());
if(!incorrectGlobalConfigurations.isEmpty()) {
List<String> commandsWithIllegalConfiguration = incorrectGlobalConfigurations
.stream()
.map(FaqCommandConfig::getFaqCommandName)
.collect(Collectors.toList());
throw new GlobalFAQCommandResponsesException(commandsWithIllegalConfiguration);
}
List<String> toBeConfiguredCommandNames = new ArrayList<>();
commands.forEach(faqCommandConfig -> {
toBeConfiguredCommandNames.add(faqCommandConfig.getFaqCommandName());
if(faqCommandConfig.getAliases() != null) {
toBeConfiguredCommandNames.addAll(faqCommandConfig.getAliases());
}
});
Set<String> uniqueToBeConfiguredCommandNames = new HashSet<>(toBeConfiguredCommandNames);
if(toBeConfiguredCommandNames.size() != uniqueToBeConfiguredCommandNames.size()) {
List<String> duplicatedKeys = getDuplicatedKeys(toBeConfiguredCommandNames);
throw new DuplicatedCommandNameOrAliasException(duplicatedKeys);
}
List<FAQCommand> allCommands = faqCommandManagementServiceBean.findAllInServer(server);
Map<String, FAQCommand> aliasMappings = getAliasMappings(allCommands);
commands.forEach(faqCommandConfig -> {
if(faqCommandConfig.getAliases() == null) return;
// find a command in which the alias is used as alias
faqCommandConfig.getAliases().forEach(aliasName -> {
if(aliasMappings.containsKey(aliasName)) {
FAQCommand faqCommand = aliasMappings.get(aliasName);
// but the command should be a different one, else we would find ourselves
if(!faqCommand.getName().equals(faqCommandConfig.getFaqCommandName())) {
throw new DuplicatedFAQCommandAliasException(faqCommand.getName(), aliasName, faqCommandConfig.getFaqCommandName());
}
}
});
});
Map<String, FAQCommand> commandMappings = getCommandMappings(allCommands);
commands.forEach(faqCommandConfig -> {
if(faqCommandConfig.getAliases() == null) return;
// check if the to be configured aliases clash with an existing command
faqCommandConfig.getAliases().forEach(aliasName -> {
if(commandMappings.containsKey(aliasName)) {
throw new FAQCommandAliasShadowingException(faqCommandConfig.getFaqCommandName(), aliasName);
}
});
});
}
private List<String> getDuplicatedKeys(List<String> toBeConfiguredCommandNames) {
Map<String, Long> collections = toBeConfiguredCommandNames.stream().collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()));
List<String> duplicatedKeys = new ArrayList<>();
collections.forEach((s, aLong) -> {
if(aLong > 1) {
duplicatedKeys.add(s);
}
});
return duplicatedKeys;
}
private void handleFAQCommand(FaqCommandConfig faqCommandConfig, AServer server) {
Optional<FAQCommand> existingFAQCommandOptional = faqCommandManagementServiceBean.findByNameAndServer(faqCommandConfig.getFaqCommandName(), server);
FAQCommand commandInstance;
if(existingFAQCommandOptional.isPresent()) {
commandInstance = existingFAQCommandOptional.get();
log.debug("Updating command {} in server {}.", commandInstance.getId(), server.getId());
updateFAQCommand(faqCommandConfig, commandInstance);
} else {
log.debug("Creating new command in server {}.", server.getId());
commandInstance = createFAQCommand(faqCommandConfig, server);
}
Set<String> existingChannelGroupNames =
commandInstance.getGroupResponses()
.stream()
.map(faqChannelGroupCommand -> faqChannelGroupCommand.getChannelGroup().getChannelGroup().getGroupName())
.collect(Collectors.toSet());
Set<String> configuredChannelGroupNames = new HashSet<>();
faqCommandConfig
.getResponses()
.forEach(faqCommandResponseConfig -> {
handleFAQChannelGroupCommand(faqCommandResponseConfig, commandInstance, server);
configuredChannelGroupNames.add(faqCommandResponseConfig.getChannelGroupName());
});
// this deletes any channel group responses which were not reflected in the configuration
existingChannelGroupNames.removeAll(configuredChannelGroupNames);
existingChannelGroupNames.forEach(channelGroupName -> {
AChannelGroup channelGroup = channelGroupManagementService.findByNameAndServerAndType(channelGroupName, server, FAQ_CHANNEL_GROUP_KEY);
FAQChannelGroup faqChannelGroup = faqChannelGroupManagementServiceBean.getFAQChannelGroupById(channelGroup.getId());
faqChannelGroupCommandManagementServiceBean.deleteFAQChannelGroupCommand(commandInstance, faqChannelGroup);
});
}
private void handleFAQChannelGroupCommand(FaqCommandResponseConfig responseConfig, FAQCommand command, AServer server) {
AChannelGroup channelGroup = channelGroupManagementService.findByNameAndServerAndType(responseConfig.getChannelGroupName(), server, FAQ_CHANNEL_GROUP_KEY);
FAQChannelGroup faqChannelGroup = faqChannelGroupManagementServiceBean.getFAQChannelGroupById(channelGroup.getId());
Optional<FAQChannelGroupCommand> existingChannelGroupCommandOptional = command.getGroupResponses()
.stream()
.filter(faqChannelGroupCommand -> faqChannelGroupCommand.getChannelGroup().getChannelGroup()
.equals(faqChannelGroup.getChannelGroup()))
.findAny();
if(command.getGlobal() && !faqChannelGroup.getGlobal()) {
log.warn("Command {} is configured as global, but to be used channel group is not defined as the global channel group.", command.getId());
throw new GlobalFAQCommandConfigMismatchException(command.getName());
}
if(faqChannelGroup.getGlobal() && !command.getGlobal()) {
log.warn("Channel group to be used is configured as global, but command {} is not. This is an illegal combination.", command.getId());
throw new GlobalFAQCommandConfigMismatchException(command.getName());
}
if(existingChannelGroupCommandOptional.isPresent()) {
FAQChannelGroupCommand existing = existingChannelGroupCommandOptional.get();
log.debug("Updating faq channel group command {} for channel group {} in server {}.", command.getId(), faqChannelGroup.getId(), server.getId());
updateFAQChannelGroupCommand(existing, responseConfig);
} else {
log.debug("Creating new faq channel group command for command {} in channel group {} in server {}.", command.getId(), faqChannelGroup.getId(), server.getId());
createFAQChannelGroupCommand(command, faqChannelGroup, responseConfig);
}
}
private void createFAQChannelGroupCommand(FAQCommand faqCommand, FAQChannelGroup faqChannelGroup, FaqCommandResponseConfig responseConfig) {
FAQChannelGroupCommand channelGroupCommand = faqChannelGroupCommandManagementServiceBean.createFAQChannelGroupCommand(faqCommand, faqChannelGroup);
log.debug("Creating {} messages.", responseConfig.getMessages().size());
for (int i = 0; i < responseConfig.getMessages().size(); i++) {
createFAQResponses(channelGroupCommand, responseConfig.getMessages().get(i), i);
}
}
private void updateFAQChannelGroupCommand(FAQChannelGroupCommand existing, FaqCommandResponseConfig responseConfig) {
Set<Integer> positions = responseConfig
.getMessages()
.stream()
.map(FaqCommandResponseMessageConfig::getPosition)
.collect(Collectors.toSet());
if(positions.size() != responseConfig.getMessages().size()) {
throw new FAQCommandResponseDuplicatedPositionException(existing.getCommand().getName(), responseConfig.getChannelGroupName());
}
List<FAQCommandResponse> existingResponses = existing
.getResponses()
.stream()
.sorted(Comparator.comparing(o -> o.getId().getPosition()))
.collect(Collectors.toList());
List<FaqCommandResponseMessageConfig> configMessages = responseConfig
.getMessages()
.stream()
.sorted(Comparator.comparingInt(FaqCommandResponseMessageConfig::getPosition))
.collect(Collectors.toList());
// re-use the existing entries, and only update if necessary, so have the appropriate update timestamp
int iterations = Math.min(configMessages.size(), existingResponses.size());
for (int i = 0; i < iterations; i++) {
mergeConfig(existingResponses.get(i), configMessages.get(i));
}
// remove all the rest, as they were not specified in the config
for(int i = iterations; i < existingResponses.size(); i++) {
faqCommandResponseManagementServiceBean.deleteCommandResponse(existingResponses.get(i));
}
for(int i = iterations; i < configMessages.size(); i++) {
createFAQResponses(existing, configMessages.get(i), i);
}
}
private void createFAQResponses(FAQChannelGroupCommand existing, FaqCommandResponseMessageConfig config, int position) {
faqCommandResponseManagementServiceBean.createResponse(config, position, existing);
}
private void mergeConfig(FAQCommandResponse existingResponse, FaqCommandResponseMessageConfig config) {
if(existingResponse.getAdditionalMessage() == null || !existingResponse.getAdditionalMessage().equals(config.getAdditionalMessage())) {
existingResponse.setAdditionalMessage(config.getAdditionalMessage());
}
FaqCommandResponseEmbedConfig embed = config.getEmbed();
if(embed != null) {
if(existingResponse.getDescription() == null || !existingResponse.getDescription().equals(embed.getDescription())) {
existingResponse.setDescription(embed.getDescription());
}
if(existingResponse.getImageURL() == null || !existingResponse.getImageURL().equals(embed.getImageUrl())) {
existingResponse.setImageURL(embed.getImageUrl());
}
if(existingResponse.getAuthorUserId() == null || !existingResponse.getAuthorUserId().equals(embed.getAuthor().getUserId())) {
existingResponse.setAuthorUserId(embed.getAuthor().getUserId());
}
mergeEmbedColor(existingResponse, embed);
} else {
existingResponse.setDescription(null);
existingResponse.setImageURL(null);
existingResponse.setAuthorUserId(null);
}
}
private void mergeEmbedColor(FAQCommandResponse existingResponse, FaqCommandResponseEmbedConfig embed) {
FaqCommandResponseEmbedColorConfig embedColor = embed.getColor();
if(existingResponse.getBlue() == null || !existingResponse.getBlue().equals(embedColor.getBlue())) {
existingResponse.setBlue(embedColor.getBlue());
}
if(existingResponse.getGreen() == null || !existingResponse.getGreen().equals(embedColor.getGreen())){
existingResponse.setGreen(embedColor.getGreen());
}
if(existingResponse.getRed() == null || !existingResponse.getRed().equals(embedColor.getRed())) {
existingResponse.setRed(embedColor.getRed());
}
}
private void updateFAQCommand(FaqCommandConfig faqCommandConfig, FAQCommand command){
if(!faqCommandConfig.getGlobal().equals(command.getGlobal())) {
command.setGlobal(faqCommandConfig.getGlobal());
}
Set<String> existingAlias;
if(command.getAliases() != null) {
existingAlias = command
.getAliases()
.stream()
.map(faqCommandAlias -> faqCommandAlias.getId().getAlias())
.collect(Collectors.toSet());
} else {
existingAlias = new HashSet<>();
}
Set<String> toBeConfigured;
if(faqCommandConfig.getAliases() != null) {
toBeConfigured = new HashSet<>(faqCommandConfig.getAliases());
} else {
toBeConfigured = new HashSet<>();
}
Set<String> aliasNeedingCreation = new HashSet<>(toBeConfigured);
aliasNeedingCreation.removeAll(existingAlias);
log.info("Creating {} aliases for command {}.", aliasNeedingCreation.size(), command.getId());
createFAQCommandAlias(command, aliasNeedingCreation);
Set<String> aliasToDelete = new HashSet<>(existingAlias);
aliasToDelete.removeAll(toBeConfigured);
log.info("Removing {} aliases for command {}.", aliasToDelete.size(), command.getId());
deleteFAQCommandAlias(command, aliasToDelete);
}
private void createFAQCommandAlias(FAQCommand command, Collection<String> aliasNames) {
aliasNames.forEach(s -> faqCommandAliasManagementService.createFAQCommandAlias(command, s));
}
private void deleteFAQCommandAlias(FAQCommand command, Collection<String> aliasName) {
aliasName.forEach(s -> faqCommandAliasManagementService.deleteFAQCommandAlias(command, s));
}
private FAQCommand createFAQCommand(FaqCommandConfig faqCommandConfig, AServer server) {
FAQCommand faqCommand = faqCommandManagementServiceBean.createFAQCommand(faqCommandConfig, server);
if(faqCommandConfig.getAliases() != null) {
createFAQCommandAlias(faqCommand, faqCommandConfig.getAliases());
}
return faqCommand;
}
private void fillDefaults(List<FaqCommandConfig> commands) {
commands
.forEach(faqCommandConfig -> {
if(faqCommandConfig.getGlobal() == null) {
faqCommandConfig.setGlobal(Boolean.FALSE);
}
faqCommandConfig
.getResponses()
.forEach(faqCommandResponse -> faqCommandResponse
.getMessages().forEach(
faqCommandResponseMessage -> {
FaqCommandResponseEmbedConfig embed = faqCommandResponseMessage.getEmbed();
if(embed == null) {
return;
}
FaqCommandResponseEmbedColorConfig color = embed.getColor();
if(color == null) {
color = FaqCommandResponseEmbedColorConfig.builder().build();
embed.setColor(color);
}
if(color.getBlue() == null || color.getBlue() == 0) {
color.setBlue(0);
}
if(color.getRed() == null || color.getRed() == 0) {
color.setRed(0);
}
if(color.getGreen() == null || color.getGreen() == 0) {
color.setGreen(0);
}
if(embed.getAuthor().getUseBot() != null && embed.getAuthor().getUseBot()) {
log.debug("Setting the BOT as the author.");
embed.getAuthor().setUserId(userService.getSelfUser().getIdLong());
}
}
));
});
}
private Map<String, FAQCommand> getCommandMappings(List<FAQCommand> commands) {
return commands
.stream()
.collect(Collectors.toMap(FAQCommand::getName, Function.identity()));
}
private Map<String, FAQCommand> getAliasMappings(List<FAQCommand> commands) {
Map<String, FAQCommand> commandMappings = commands
.stream()
.collect(Collectors.toMap(FAQCommand::getName, Function.identity()));
commands
.forEach(faqCommand -> faqCommand
.getAliases()
.forEach(faqCommandAlias ->
commandMappings.put(faqCommandAlias.getId().getAlias(), faqCommand)));
return commandMappings;
}
public List<FaqCommandConfig> loadFAQCommandsFromJson(String json) {
FaqCommandConfig[] faqCommandConfigs = gson.fromJson(json, FaqCommandConfig[].class);
return Arrays.asList(faqCommandConfigs);
}
public void deleteFAQCommand(String commandToDelete, AServer server) {
FAQCommand toDelete = faqCommandManagementServiceBean.findByNameAndServer(commandToDelete, server)
.orElseThrow(() -> new FAQCommandNotFoundException(commandToDelete));
faqCommandManagementServiceBean.deleteFAQCommand(toDelete);
}
public String exportFAQConfig(String commandToExport, AServer server) {
FAQCommand toExport = faqCommandManagementServiceBean.findByNameAndServer(commandToExport, server)
.orElseThrow(() -> new FAQCommandNotFoundException(commandToExport));
return configConverter.serializeCommands(Arrays.asList(toExport));
}
public String exportFAQConfig(AServer server) {
List<FAQCommand> toExport = faqCommandManagementServiceBean.findAllInServer(server);
log.info("Exporting {} FAQ commands for server {}.", toExport.size(), server.getId());
return configConverter.serializeCommands(toExport);
}
public ListFAQCommandsModel getCommandListingForServer(AServer server) {
List<FAQCommand> faqCommands = faqCommandManagementServiceBean.findAllInServer(server);
return listFAQCommandsModelConverter.fromFAQCommands(faqCommands);
}
}

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