82 Commits

Author SHA1 Message Date
8e17e55341 move to 0.9.3-snapshot
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-09-06 22:14:58 +02:00
1f2bafa7f8 (wip) migrate commands to components
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-06 22:14:44 +02:00
eafa80f1d2 rename command classes
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-06 21:47:23 +02:00
5ad1d9e891 start moving to @Components
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-06 21:44:55 +02:00
dcc4785edc edit text
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-06 17:59:28 +02:00
a1192db62b fix timeout duration check
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 13:15:39 +02:00
910b7a406c show lowercase version
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 13:11:43 +02:00
eb953390e3 fix ver
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-09-05 13:07:22 +02:00
5d8fc55276 move to 0.9.2-snapshot
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-09-05 13:03:15 +02:00
784ce11d6b remove old #0000 discriminator
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 13:02:59 +02:00
799891ad6b move to v0.9.1-snapshot
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 12:50:00 +02:00
0c575378f3 fix concurrency and ack
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 12:49:37 +02:00
5015e82700 fix build jar
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-09-05 01:53:05 +02:00
29f486566b move util to service
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-09-05 01:35:15 +02:00
f80a49995b (wip) migrate to spring beans
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 01:32:46 +02:00
bbd0299103 Merge branch 'main' into develop
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 00:06:44 +02:00
fd2970fa59 cleanup and reformat
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 00:06:35 +02:00
bb674240bb (wip) switch to spring/hibernate
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 00:05:51 +02:00
5a34e5994e add spring/hibernate deps
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-04 23:42:48 +02:00
14b54501fd update drone
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-04 23:42:30 +02:00
1928cfe858 update readme
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-04 23:41:54 +02:00
0b7880af88 fix drone
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-04 23:36:49 +02:00
f1969c2043 Update README.MD
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-01 21:02:56 +02:00
acad4bad8b Merge pull request 'Update dependency org.junit.jupiter:junit-jupiter-api to v5.13.0' (#47) from renovate/org.junit.jupiter-junit-jupiter-api-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #47
2025-06-01 20:25:10 +02:00
4ce9acd428 Merge pull request 'Update dependency org.json:json to v20250517' (#46) from renovate/org.json-json-20250517.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #46
2025-06-01 20:25:04 +02:00
2ab52bd713 Merge pull request 'Update dependency com.google.protobuf:protobuf-java to v4.31.1' (#45) from renovate/com.google.protobuf-protobuf-java-4.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #45
2025-06-01 20:24:56 +02:00
f38e34c1ce Merge pull request 'Update dependency com.google.code.gson:gson to v2.13.1' (#43) from renovate/com.google.code.gson-gson-2.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #43
2025-06-01 20:24:46 +02:00
f87854459f Merge pull request 'Update dependency net.dv8tion:JDA to v5.5.1' (#42) from renovate/net.dv8tion-jda-5.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #42
2025-06-01 20:24:31 +02:00
72c4ae2133 Update dependency org.junit.jupiter:junit-jupiter-api to v5.13.0
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-30 11:00:27 +00:00
7f4ca6aa8e Update dependency com.google.protobuf:protobuf-java to v4.31.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-28 20:00:24 +00:00
c3793aa159 Update dependency org.json:json to v20250517
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-17 14:00:27 +00:00
63fc1feaea Update dependency net.dv8tion:JDA to v5.5.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-05-03 12:00:25 +00:00
71d646ff69 Update dependency com.google.code.gson:gson to v2.13.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-04-24 02:00:23 +00:00
48d537d2db Merge pull request 'Update dependency com.google.code.gson:gson to v2.13.0' (#41) from renovate/com.google.code.gson-gson-2.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #41
2025-04-13 00:55:18 +02:00
136447b9df Merge pull request 'Update dependency org.junit.jupiter:junit-jupiter-api to v5.12.2' (#40) from renovate/org.junit.jupiter-junit-jupiter-api-5.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #40
2025-04-13 00:54:58 +02:00
372949a9e0 Merge pull request 'Update dependency org.apache.commons:commons-text to v1.13.1' (#39) from renovate/org.apache.commons-commons-text-1.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #39
2025-04-13 00:54:46 +02:00
4f4549a81e Merge pull request 'Update dependency org.owasp:dependency-check-maven to v12.1.1' (#38) from renovate/org.owasp-dependency-check-maven-12.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #38
2025-04-13 00:54:33 +02:00
b0dd1c21b2 Merge pull request 'Update dependency net.dv8tion:JDA to v5.3.2' (#37) from renovate/net.dv8tion-jda-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #37
2025-04-13 00:54:12 +02:00
d46368f0ce Update dependency com.google.code.gson:gson to v2.13.0
Some checks failed
continuous-integration/drone/pr Build is failing
2025-04-11 15:00:26 +00:00
2032dc1d0e Update dependency org.junit.jupiter:junit-jupiter-api to v5.12.2
Some checks failed
continuous-integration/drone/pr Build is failing
2025-04-11 15:00:24 +00:00
9c099230c9 Update dependency org.apache.commons:commons-text to v1.13.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-04-10 23:00:22 +00:00
f2134cbdb9 Update dependency org.owasp:dependency-check-maven to v12.1.1
Some checks failed
continuous-integration/drone/pr Build is failing
2025-04-05 13:00:31 +00:00
97e846c3dc Update dependency net.dv8tion:JDA to v5.3.2
All checks were successful
continuous-integration/drone/pr Build is passing
2025-04-05 11:00:29 +00:00
7f16a011b3 update pipeline
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2025-03-29 16:45:14 +01:00
b32ece3f88 Update .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-03-29 13:02:17 +01:00
681785ef0d Merge pull request 'Update dependency net.dv8tion:JDA to v5.3.1' (#36) from renovate/net.dv8tion-jda-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #36
2025-03-28 14:07:57 +01:00
a72b6f690b Update dependency net.dv8tion:JDA to v5.3.1
All checks were successful
continuous-integration/drone/pr Build is passing
2025-03-27 18:00:34 +00:00
645fb5f4a6 build on arm
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-27 00:37:54 +01:00
a228067cce update drone
Some checks failed
continuous-integration/drone/push Build is failing
2025-03-27 00:36:31 +01:00
4f0eb7ce74 Update .drone.yml
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-27 00:22:01 +01:00
a7e36299b6 Update .drone.yml
Some checks reported errors
continuous-integration/drone/push Build was killed
2025-03-27 00:21:29 +01:00
1e7c43e360 Merge pull request 'Update dependency com.google.protobuf:protobuf-java to v4.30.2' (#35) from renovate/com.google.protobuf-protobuf-java-4.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #35
2025-03-26 23:21:50 +01:00
2a4a80cc3f Update dependency com.google.protobuf:protobuf-java to v4.30.2
All checks were successful
continuous-integration/drone/pr Build is passing
2025-03-26 20:00:37 +00:00
a50e8c050b update pull requests pipe
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-25 18:26:00 +01:00
f7c1b096bc Merge pull request 'Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5.1.0.4751' (#34) from renovate/org.sonarsource.scanner.maven-sonar-maven-plugin-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #34
2025-03-25 14:06:04 +01:00
884814064c Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5.1.0.4751
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-25 11:00:43 +00:00
ce0938bc2c Merge pull request 'Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5' (#33) from renovate/org.sonarsource.scanner.maven-sonar-maven-plugin-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
Reviewed-on: #33
2025-03-23 12:55:48 +01:00
aabfbd3020 Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v5
Some checks reported errors
continuous-integration/drone/pr Build was killed
2025-03-23 11:28:03 +00:00
25bc5a3ef2 Merge pull request 'Update dependency commons-codec:commons-codec to v1.18.0' (#25) from renovate/commons-codec-commons-codec-1.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #25
2025-03-23 11:33:45 +01:00
ab52f30eb2 Merge pull request 'Update dependency org.apache.commons:commons-text to v1.13.0' (#26) from renovate/org.apache.commons-commons-text-1.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #26
2025-03-23 11:33:36 +01:00
2e0c2e4e14 Merge pull request 'Update dependency org.apache.maven.plugins:maven-javadoc-plugin to v3.11.2' (#27) from renovate/org.apache.maven.plugins-maven-javadoc-plugin-3.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #27
2025-03-23 11:33:29 +01:00
edf896efb0 Merge pull request 'Update dependency org.yaml:snakeyaml to v2.4' (#31) from renovate/org.yaml-snakeyaml-2.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #31
2025-03-23 11:33:20 +01:00
bd7355add9 Merge pull request 'Update dependency org.json:json to v20250107' (#32) from renovate/org.json-json-20250107.x into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #32
2025-03-23 11:33:05 +01:00
6d210551af Update dependency org.json:json to v20250107
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 10:25:08 +00:00
abd3c02be6 Update dependency org.yaml:snakeyaml to v2.4
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 10:25:06 +00:00
50749f2108 Update dependency org.apache.maven.plugins:maven-javadoc-plugin to v3.11.2
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 10:25:04 +00:00
fc7b6d54d1 Update dependency org.apache.commons:commons-text to v1.13.0
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 10:25:01 +00:00
f5d684c5a4 Update dependency commons-codec:commons-codec to v1.18.0
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 10:24:59 +00:00
5ea12aa693 Merge pull request 'Update dependency org.junit.jupiter:junit-jupiter-api to v5.12.1' (#29) from renovate/org.junit.jupiter-junit-jupiter-api-5.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #29
2025-03-23 10:38:52 +01:00
5c4f7e4252 Merge pull request 'Update dependency org.jsoup:jsoup to v1.19.1' (#28) from renovate/org.jsoup-jsoup-1.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #28
2025-03-23 10:38:35 +01:00
d9dcde7560 Merge pull request 'Update dependency com.google.code.gson:gson to v2.12.1' (#24) from renovate/com.google.code.gson-gson-2.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #24
2025-03-23 10:38:05 +01:00
64762a9a4f Merge pull request 'Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v3.11.0.3922' (#30) from renovate/org.sonarsource.scanner.maven-sonar-maven-plugin-3.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #30
2025-03-23 10:37:32 +01:00
4542985431 Merge pull request 'Update dependency org.slf4j:slf4j-simple to v2.0.17' (#23) from renovate/org.slf4j-slf4j-simple-2.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #23
2025-03-23 10:31:39 +01:00
88d4b6461b Merge pull request 'Update dependency org.slf4j:slf4j-api to v2.0.17' (#22) from renovate/org.slf4j-slf4j-api-2.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #22
2025-03-23 10:31:28 +01:00
e8f1c85f08 Merge pull request 'Update dependency com.google.protobuf:protobuf-java to v4.30.1' (#21) from renovate/com.google.protobuf-protobuf-java-4.x into main
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: #21
2025-03-23 10:31:14 +01:00
1ed389c18b Update dependency org.sonarsource.scanner.maven:sonar-maven-plugin to v3.11.0.3922
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 08:25:02 +00:00
4320c9698a Update dependency org.junit.jupiter:junit-jupiter-api to v5.12.1
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 08:25:00 +00:00
3a8044dda1 Update dependency org.jsoup:jsoup to v1.19.1
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 07:25:07 +00:00
96ca58de12 Update dependency com.google.code.gson:gson to v2.12.1
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 05:24:54 +00:00
5ed444ab92 Update dependency org.slf4j:slf4j-simple to v2.0.17
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 05:24:51 +00:00
19a5583594 Update dependency org.slf4j:slf4j-api to v2.0.17
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 04:27:18 +00:00
a39bcf68cb Update dependency com.google.protobuf:protobuf-java to v4.30.1
Some checks failed
continuous-integration/drone/pr Build is failing
2025-03-23 04:27:16 +00:00
100 changed files with 2374 additions and 1832 deletions

View File

@@ -1,77 +1,14 @@
kind: pipeline
type: docker
name: build
platform:
os: linux
kind: template
load: java-build-deploy.yaml
data:
arch: arm64
trigger:
branch:
os: linux
build_branches:
- main
event:
- develop
build_events:
- push
- pull_request
steps:
# test if it compiles correctly
- name: build
image: maven:3-eclipse-temurin-21
commands:
- mvn verify --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V
# run unit tests
- name: test
image: maven:3-eclipse-temurin-21
commands:
- mvn test --no-transfer-progress -B -V
# check maven dependencies
- name: dependency-check
image: maven:3-eclipse-temurin-21
commands:
- mvn dependency-check:check --no-transfer-progress -B -V -DnvdApiKey=$NVD_API_KEY
environment:
NVD_API_KEY:
from_secret: nvd_api_key
# run code analysis
- name: code-analysis
image: maven:3-eclipse-temurin-21
commands:
- mvn sonar:sonar --no-transfer-progress -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.host.url=$SONAR_INSTANCE_URL -Dsonar.token=$SONAR_LOGIN_KEY -B -V
environment:
SONAR_PROJECT_KEY:
from_secret: sonar_project_key
SONAR_INSTANCE_URL:
from_secret: sonar_instance_url
SONAR_LOGIN_KEY:
from_secret: sonar_login_key
---
kind: pipeline
type: docker
name: deploy
platform:
os: linux
arch: arm64
trigger:
event:
- promote
target:
sonar_project_key: HidekoBot
deploy_targets:
- production
steps:
# skip all previous steps because they were already ran in the "build" phase; we don't need to re-analyze the code.
# upload to maven repository
- name: maven-deploy
image: maven:3-eclipse-temurin-21
commands:
- mvn deploy --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V -gs settings.xml -Dmaven.repo.username=$MAVEN_REPO_USERNAME -Dmaven.repo.password=$MAVEN_REPO_PASSWORD
environment:
MAVEN_REPO_USERNAME:
from_secret: maven_repo_username
MAVEN_REPO_PASSWORD:
from_secret: maven_repo_password

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
target/
.idea/
scripts/
*.sqlite

View File

@@ -0,0 +1,88 @@
---
kind: pipeline
type: docker
name: build
platform:
os: {{ .input.os }}
arch: {{ .input.arch }}
trigger:
branch:
{{- range .input.build_branches }}
- {{ . }}
{{- end }}
event:
{{- range .input.build_events }}
- {{ . }}
{{- end }}
# Global project-specific environment variables
environment:
{{- range .input.envs }}
{{ .name }}: {{ .value }}
{{- end }}
steps:
# Test if it compiles correctly
- name: build
image: maven:3-eclipse-temurin-21
commands:
- mvn verify --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V
# Run unit tests
- name: test
image: maven:3-eclipse-temurin-21
commands:
- mvn test --no-transfer-progress -B -V
# Check maven dependencies
- name: dependency-check
image: owasp/dependency-check:latest
commands:
- dependency-check --scan /src --format ALL --out /src/target --nvdApiKey $NVD_API_KEY
environment:
NVD_API_KEY:
from_secret: nvd_api_key
# Run code analysis
- name: code-analysis
when:
event:
- push
image: maven:3-eclipse-temurin-21
commands:
- mvn sonar:sonar --no-transfer-progress -Dsonar.projectKey={{ .input.sonar_project_key }} -Dsonar.host.url=$SONAR_INSTANCE_URL -Dsonar.token=$SONAR_LOGIN_KEY -B -V
environment:
SONAR_INSTANCE_URL:
from_secret: sonar_instance_url
SONAR_LOGIN_KEY:
from_secret: sonar_login_key
---
kind: pipeline
type: kubernetes
name: deploy
trigger:
event:
- promote
target:
{{- range .input.deploy_targets }}
- {{ . }}
{{- end }}
# Global project-specific environment variables
environment:
{{- range .input.envs }}
{{ .name }}: {{ .value }}
{{- end }}
steps:
# Upload to Maven repository
- name: maven-deploy
image: maven:3-eclipse-temurin-21
commands:
- mvn deploy --no-transfer-progress -DskipTests=true -Dmaven.javadoc.skip=true -B -V -gs settings.xml -Dmaven.repo.username=$MAVEN_REPO_USERNAME -Dmaven.repo.password=$MAVEN_REPO_PASSWORD
environment:
MAVEN_REPO_USERNAME:
from_secret: maven_repo_username
MAVEN_REPO_PASSWORD:
from_secret: maven_repo_password

View File

@@ -1,9 +1,9 @@
# HidekoBot
[![Reliability Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot_AYWyYHsvX-1Ma0D4pJ59&metric=reliability_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot_AYWyYHsvX-1Ma0D4pJ59)
[![Maintainability Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot_AYWyYHsvX-1Ma0D4pJ59&metric=sqale_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot_AYWyYHsvX-1Ma0D4pJ59)
[![Security Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot_AYWyYHsvX-1Ma0D4pJ59&metric=security_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot_AYWyYHsvX-1Ma0D4pJ59)
[![Reliability Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot&metric=reliability_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot)
[![Maintainability Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot&metric=sqale_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot)
[![Security Rating](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot&metric=security_rating&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot)
[![Build Status](https://drone.beatrice.wtf/api/badges/bea/HidekoBot/status.svg)](https://drone.beatrice.wtf/bea/HidekoBot)
[![Lines of Code](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot_AYWyYHsvX-1Ma0D4pJ59&metric=ncloc&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot_AYWyYHsvX-1Ma0D4pJ59)
[![Lines of Code](https://sonar.beatrice.wtf/api/project_badges/measure?project=HidekoBot&metric=ncloc&token=0a63c149148555d6d2ee40665af1afae8f67cc3f)](https://sonar.beatrice.wtf/dashboard?id=HidekoBot)
Hideko is a general-purpose Discord bot.

107
pom.xml
View File

@@ -6,14 +6,16 @@
<groupId>wtf.beatrice.hidekobot</groupId>
<artifactId>HidekoBot</artifactId>
<version>0.6.3-SNAPSHOT</version>
<version>0.9.3-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.dependencyCheck.htmlReportPath>./target/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
<sonar.dependencyCheck.jsonReportPath>./target/dependency-check-report.json</sonar.dependencyCheck.jsonReportPath>
<sonar.dependencyCheck.htmlReportPath>./target/dependency-check-report.html
</sonar.dependencyCheck.htmlReportPath>
<sonar.dependencyCheck.jsonReportPath>./target/dependency-check-report.json
</sonar.dependencyCheck.jsonReportPath>
<sonar.dependencyCheck.summarize>true</sonar.dependencyCheck.summarize>
</properties>
@@ -22,18 +24,13 @@
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.3.0</version>
<version>5.5.1</version>
</dependency>
<!-- JDA depends on SLF4J for logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.6</version>
<version>2.0.17</version>
</dependency>
<!-- Dependency used for SQLite database connections-->
@@ -47,26 +44,26 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
<version>2.4</version>
</dependency>
<!-- JSoup is used to parse HTML into JSON objects for better handling in Java -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
<version>1.19.1</version>
</dependency>
<!-- Various String manipulation utils -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
<version>1.13.1</version>
</dependency>
<!-- JSON dependency used for better parsing of JSON files -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
<version>20250517</version>
</dependency>
<!-- Start Random.org dependencies -->
@@ -78,12 +75,12 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<version>1.18.0</version>
</dependency>
<!-- End Random.org dependencies -->
@@ -91,9 +88,34 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<version>5.13.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.4.5</version>
</dependency>
<!-- Hibernate core + SQLite dialects -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.6.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
<version>6.6.1.Final</version>
</dependency>
<!-- Jakarta Persistence API (usually provided via Hibernate, but include explicitly for compile-time types) -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
<!-- override dependencies to use newer versions -->
@@ -102,7 +124,7 @@
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.30.0</version>
<version>4.31.1</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -117,49 +139,29 @@
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>wtf.beatrice.hidekobot.HidekoBot</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<version>3.11.2</version>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
<version>5.1.0.4751</version>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>12.1.0</version>
<version>12.1.1</version>
<configuration>
<failBuildOnCVSS>8</failBuildOnCVSS>
<!--suppress UnresolvedMavenProperty -->
<nvdApiKey>${nvdApiKey}</nvdApiKey>
<knownExploitedUrl>
https://raw.githubusercontent.com/EugenMayer/cisa-known-exploited-mirror/main/known_exploited_vulnerabilities.json
</knownExploitedUrl>
<formats>
<format>html</format>
<format>json</format>
@@ -170,6 +172,23 @@
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.4.5</version>
<configuration>
<!-- Replace the main artifact (no classifier) -->
<classifier></classifier>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -5,12 +5,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wtf.beatrice.hidekobot.datasources.ConfigurationEntry;
import wtf.beatrice.hidekobot.datasources.ConfigurationSource;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.datasources.PropertiesSource;
import wtf.beatrice.hidekobot.listeners.MessageCommandListener;
import wtf.beatrice.hidekobot.listeners.MessageLogger;
import wtf.beatrice.hidekobot.listeners.SlashCommandCompletionListener;
import wtf.beatrice.hidekobot.listeners.SlashCommandListener;
import wtf.beatrice.hidekobot.util.Services;
import java.awt.*;
import java.lang.reflect.Field;
@@ -22,15 +22,27 @@ import java.util.concurrent.ScheduledExecutorService;
public class Cache
{
private Cache() {
private Cache()
{
throw new IllegalStateException("Utility class");
}
// todo: make this compatible with the message listener's regex
private static final String BOT_PREFIX = "hideko";
private static final Logger LOGGER = LoggerFactory.getLogger(Cache.class);
private static Services SERVICES;
public static void setServices(Services services)
{
SERVICES = services;
}
public static Services getServices()
{
return SERVICES;
}
// map to store results of "love calculator", to avoid people re-running the same command until
// they get what they wanted.
// i didn't think this was worthy of a whole database table with a runnable checking for expiration,
@@ -39,7 +51,6 @@ public class Cache
private static PropertiesSource propertiesSource = null;
private static ConfigurationSource configurationSource = null;
private static DatabaseSource databaseSource = null;
private static boolean verbose = false;
private static MessageLogger verbosityLogger = null;
private static final long BOT_MAINTAINER_ID = 979809420714332260L;
@@ -78,7 +89,10 @@ public class Cache
*
* @return array of supported resolutions.
*/
public static int[] getSupportedAvatarResolutions() { return supportedAvatarResolutions; }
public static int[] getSupportedAvatarResolutions()
{
return supportedAvatarResolutions;
}
/**
@@ -86,7 +100,10 @@ public class Cache
*
* @return a boolean which is true if the bot is in verbose-mode
*/
public static synchronized boolean isVerbose() { return verbose; }
public static synchronized boolean isVerbose()
{
return verbose;
}
/**
* Set the bot's verbosity status at runtime.
@@ -116,7 +133,8 @@ public class Cache
*
* @return a long of the account's id
*/
public static long getBotOwnerId() {
public static long getBotOwnerId()
{
return configurationSource == null ? 0L : (Long) configurationSource.getConfigValue(ConfigurationEntry.BOT_OWNER_ID);
}
@@ -126,7 +144,8 @@ public class Cache
*
* @return a String of the bot's token.
*/
public static String getBotToken() {
public static String getBotToken()
{
return configurationSource == null ? null : (String) configurationSource.getConfigValue(ConfigurationEntry.BOT_TOKEN);
}
@@ -137,7 +156,10 @@ public class Cache
* @return a long of the account's id
*/
public static long getBotMaintainerId() { return BOT_MAINTAINER_ID; }
public static long getBotMaintainerId()
{
return BOT_MAINTAINER_ID;
}
/**
* Set the bot's application id.
@@ -154,34 +176,21 @@ public class Cache
*
* @return a string of the bot's application id
*/
public static String getBotApplicationId() { return botApplicationId; }
public static String getBotApplicationId()
{
return botApplicationId;
}
/**
* Function to generate an invite link for the bot
*
* @return a string containing the invite link
*/
public static String getInviteUrl() {
public static String getInviteUrl()
{
return DEFAULT_INVITE_LINK.replace("%userid%", botApplicationId);
}
/**
* Set the already fully-initialized DatabaseSource instance, ready to be accessed and used.
*
* @param databaseSourceInstance the fully-initialized DatabaseSource instance.
*/
public static void setDatabaseSourceInstance(DatabaseSource databaseSourceInstance)
{
databaseSource = databaseSourceInstance;
}
/**
* Get the fully-initialized DatabaseSource instance, ready to be used.
*
* @return the DatabaseSource instance.
*/
public static @Nullable DatabaseSource getDatabaseSource() { return databaseSource; }
/**
* Set the properties source instance loaded from the JAR archive.
*
@@ -197,25 +206,35 @@ public class Cache
*
* @return the String of the DateTimeFormatter format.
*/
public static String getExpiryTimestampFormat(){ return EXPIRY_TIMESTAMP_FORMAT; }
public static String getExpiryTimestampFormat()
{
return EXPIRY_TIMESTAMP_FORMAT;
}
/**
* Get the amount of seconds after which a message expires.
*
* @return long value of the expiry seconds.
*/
public static long getExpiryTimeSeconds() { return EXPIRY_TIME_SECONDS; }
public static long getExpiryTimeSeconds()
{
return EXPIRY_TIME_SECONDS;
}
public static String getBotName() { return BOT_NAME; }
public static String getBotName()
{
return BOT_NAME;
}
/**
* Get the bot's version.
*
* @return a String of the bot version.
*/
public static String getBotVersion() {
return propertiesSource.getProperty("bot.version");
public static String getBotVersion()
{
return propertiesSource.getProperty("bot.version").toLowerCase();
}
/**
@@ -223,7 +242,8 @@ public class Cache
*
* @return a String containing the base URL of the repository, including a <b>trailing slash</b>.
*/
public static String getRepositoryUrl() {
public static String getRepositoryUrl()
{
String url = propertiesSource.getProperty("repo.base_url");
return url.endsWith("/") ? url : url + "/";
}
@@ -233,16 +253,19 @@ public class Cache
*
* @return the Color object.
*/
public static Color getBotColor() {
public static Color getBotColor()
{
Color defaultColor = Color.PINK;
if (configurationSource == null) return defaultColor;
String colorName = (String) configurationSource.getConfigValue(ConfigurationEntry.BOT_COLOR);
Color color = null;
try {
try
{
Field field = Color.class.getField(colorName);
color = (Color) field.get(null);
} catch (RuntimeException | NoSuchFieldException | IllegalAccessException e) {
} catch (RuntimeException | NoSuchFieldException | IllegalAccessException e)
{
LOGGER.error("Unknown color: {}", colorName);
}
return color == null ? defaultColor : color;
@@ -251,21 +274,36 @@ public class Cache
//todo javadocs
public static void setSlashCommandListener(SlashCommandListener commandListener)
{ slashCommandListener = commandListener; }
{
slashCommandListener = commandListener;
}
public static SlashCommandListener getSlashCommandListener() { return slashCommandListener; }
public static SlashCommandListener getSlashCommandListener()
{
return slashCommandListener;
}
public static void setSlashCommandCompletionListener(SlashCommandCompletionListener commandCompletionListener)
{ slashCommandCompletionListener = commandCompletionListener; }
{
slashCommandCompletionListener = commandCompletionListener;
}
public static SlashCommandCompletionListener getSlashCommandCompletionListener() { return slashCommandCompletionListener; }
public static SlashCommandCompletionListener getSlashCommandCompletionListener()
{
return slashCommandCompletionListener;
}
public static void setMessageCommandListener(MessageCommandListener commandListener)
{ messageCommandListener = commandListener; }
{
messageCommandListener = commandListener;
}
public static MessageCommandListener getMessageCommandListener() { return messageCommandListener; }
public static MessageCommandListener getMessageCommandListener()
{
return messageCommandListener;
}
/**
* Set the bot's startup time. Generally only used at boot time.
@@ -273,7 +311,9 @@ public class Cache
* @param time a LocalDateTime of the startup moment.
*/
public static void setStartupTime(LocalDateTime time)
{ startupTime = time; }
{
startupTime = time;
}
/**
@@ -281,37 +321,54 @@ public class Cache
*
* @return a LocalDateTime object of the startup instant.
*/
public static LocalDateTime getStartupTime() { return startupTime; }
public static LocalDateTime getStartupTime()
{
return startupTime;
}
/**
* Get the time of when the bot was created.
*
* @return a LocalDateTime object of the first commit's instant.
*/
public static LocalDateTime getBotBirthDate() { return botBirthDate; }
public static LocalDateTime getBotBirthDate()
{
return botBirthDate;
}
public static String getFullHeartBeatLink() {
public static String getFullHeartBeatLink()
{
return configurationSource == null ? null : (String) configurationSource.getConfigValue(ConfigurationEntry.HEARTBEAT_LINK);
}
//todo javadocs
public static String getExecPath() { return EXEC_PATH; }
public static String getExecPath()
{
return EXEC_PATH;
}
/*private static ConfigurationSource getConfigurationSource()
{ return configurationSource; }*/
public static String getRandomOrgApiKey() {
public static String getRandomOrgApiKey()
{
return configurationSource == null ? null : (String) configurationSource.getConfigValue(ConfigurationEntry.RANDOM_ORG_API_KEY);
}
public static void setConfigurationSource(ConfigurationSource configurationSource)
{ Cache.configurationSource = configurationSource; }
{
Cache.configurationSource = configurationSource;
}
/**
* Get the bot's prefix
*
* @return a String of the bot's prefix.
*/
public static String getBotPrefix() { return BOT_PREFIX; }
public static String getBotPrefix()
{
return BOT_PREFIX;
}
public static void cacheLoveCalculatorValue(String userId1, String userId2, int value)
{
@@ -336,7 +393,8 @@ public class Cache
loveCalculatorValues.remove(userId2 + "|" + userId1);
}
public static ScheduledExecutorService getTaskScheduler() {
public static ScheduledExecutorService getTaskScheduler()
{
return taskScheduler;
}

View File

@@ -6,20 +6,25 @@ import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.requests.GatewayIntent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.context.ConfigurableApplicationContext;
import wtf.beatrice.hidekobot.commands.completer.ProfileImageCommandCompleter;
import wtf.beatrice.hidekobot.commands.message.HelloCommand;
import wtf.beatrice.hidekobot.commands.message.*;
import wtf.beatrice.hidekobot.commands.slash.*;
import wtf.beatrice.hidekobot.datasources.ConfigurationSource;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.datasources.PropertiesSource;
import wtf.beatrice.hidekobot.listeners.*;
import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask;
import wtf.beatrice.hidekobot.runnables.HeartBeatTask;
import wtf.beatrice.hidekobot.runnables.RandomOrgSeedTask;
import wtf.beatrice.hidekobot.runnables.StatusUpdateTask;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.services.CommandService;
import wtf.beatrice.hidekobot.services.DatabaseService;
import wtf.beatrice.hidekobot.util.FormatUtil;
import wtf.beatrice.hidekobot.util.RandomUtil;
import wtf.beatrice.hidekobot.util.Services;
import java.io.File;
import java.time.LocalDateTime;
@@ -30,6 +35,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@SpringBootApplication
public class HidekoBot
{
private static JDA jda;
@@ -63,6 +69,18 @@ public class HidekoBot
return;
}
ApplicationHome home = new ApplicationHome(HidekoBot.class);
System.setProperty("APP_HOME", home.getDir().getAbsolutePath());
ConfigurableApplicationContext context = SpringApplication.run(HidekoBot.class, args);
CommandService commandService = context.getBean(CommandService.class);
DatabaseService databaseService = context.getBean(DatabaseService.class);
Services services = new wtf.beatrice.hidekobot.util.Services(
commandService,
databaseService
);
Cache.setServices(services);
try
{
// try to create the bot object and authenticate it with discord.
@@ -76,12 +94,12 @@ public class HidekoBot
);
jda = jdaBuilder.build().awaitReady();
} catch (InterruptedException e) {
} catch (InterruptedException e)
{
LOGGER.error(e.getMessage()); // print the error message, omit the stack trace.
Thread.currentThread().interrupt(); // send interrupt to the thread.
shutdown(); // if we failed connecting and authenticating, then quit.
}
catch (Exception e)
} catch (Exception e)
{
LOGGER.error(e.getMessage()); // print the error message, omit the stack trace.
shutdown(); // if we failed connecting and authenticating, then quit.
@@ -96,7 +114,8 @@ public class HidekoBot
// if there is at least one arg, then iterate through them because we have additional things to do.
// we are doing this at the end because we might need the API to be already initialized for some things.
if(args.length > 0) {
if (args.length > 0)
{
List<String> argsList = new ArrayList<>(Arrays.asList(args));
@@ -113,7 +132,6 @@ public class HidekoBot
}
boolean enableRandomSeedUpdaterTask = false;
// initialize random.org object if API key is provided
{
@@ -127,53 +145,55 @@ public class HidekoBot
}
// register slash commands and completers
SlashCommandListener slashCommandListener = new SlashCommandListener();
SlashCommandCompletionListener slashCommandCompletionListener = new SlashCommandCompletionListener();
AvatarCommand avatarCommand = new AvatarCommand();
ProfileImageCommandCompleter avatarCommandCompleter = new ProfileImageCommandCompleter(avatarCommand);
slashCommandListener.registerCommand(avatarCommand);
SlashCommandListener slashCommandListener = context.getBean(SlashCommandListener.class);
SlashCommandCompletionListener slashCommandCompletionListener = context.getBean(SlashCommandCompletionListener.class);
MessageCommandListener messageCommandListener = context.getBean(MessageCommandListener.class);
ButtonInteractionListener buttonInteractionListener = context.getBean(ButtonInteractionListener.class);
SelectMenuInteractionListener selectMenuInteractionListener = context.getBean(SelectMenuInteractionListener.class);
SlashAvatarCommand slashAvatarCommand = context.getBean(SlashAvatarCommand.class);
ProfileImageCommandCompleter avatarCommandCompleter = new ProfileImageCommandCompleter(slashAvatarCommand);
slashCommandListener.registerCommand(slashAvatarCommand);
slashCommandCompletionListener.registerCommandCompleter(avatarCommandCompleter);
slashCommandListener.registerCommand(new BanCommand());
BannerCommand bannerCommand = new BannerCommand();
ProfileImageCommandCompleter bannerCommandCompleter = new ProfileImageCommandCompleter(bannerCommand);
slashCommandListener.registerCommand(bannerCommand);
slashCommandListener.registerCommand(context.getBean(SlashBanCommand.class));
SlashBannerCommand slashBannerCommand = context.getBean(SlashBannerCommand.class);
ProfileImageCommandCompleter bannerCommandCompleter = new ProfileImageCommandCompleter(slashBannerCommand);
slashCommandListener.registerCommand(slashBannerCommand);
slashCommandCompletionListener.registerCommandCompleter(bannerCommandCompleter);
slashCommandListener.registerCommand(new BotInfoCommand());
slashCommandListener.registerCommand(new ClearCommand());
slashCommandListener.registerCommand(new CoinFlipCommand());
slashCommandListener.registerCommand(new DiceRollCommand());
slashCommandListener.registerCommand(new DieCommand());
slashCommandListener.registerCommand(new HelpCommand());
slashCommandListener.registerCommand(new InviteCommand());
slashCommandListener.registerCommand(new KickCommand());
slashCommandListener.registerCommand(new LoveCalculatorCommand());
slashCommandListener.registerCommand(new MagicBallCommand());
slashCommandListener.registerCommand(new PingCommand());
slashCommandListener.registerCommand(new SayCommand());
slashCommandListener.registerCommand(new TimeoutCommand());
slashCommandListener.registerCommand(new TriviaCommand());
slashCommandListener.registerCommand(new UrbanDictionaryCommand());
slashCommandListener.registerCommand(context.getBean(SlashBotInfoCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashClearCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashCoinFlipCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashDiceRollCommand.class));
slashCommandListener.registerCommand(new SlashDieCommand());
slashCommandListener.registerCommand(new SlashHelpCommand());
slashCommandListener.registerCommand(context.getBean(SlashInviteCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashKickCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashLoveCalculatorCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashMagicBallCommand.class));
slashCommandListener.registerCommand(new SlashPingCommand());
slashCommandListener.registerCommand(context.getBean(SlashSayCommand.class));
slashCommandListener.registerCommand(context.getBean(SlashTimeoutCommand.class));
slashCommandListener.registerCommand(new SlashTriviaCommand());
slashCommandListener.registerCommand(new SlashUrbanDictionaryCommand());
// register message commands
MessageCommandListener messageCommandListener = new MessageCommandListener();
messageCommandListener.registerCommand(new HelloCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AliasCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AvatarCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.BanCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.BannerCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.BotInfoCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.CoinFlipCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.ClearCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.DiceRollCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.HelpCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.InviteCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.KickCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.LoveCalculatorCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.MagicBallCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.SayCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.TimeoutCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.TriviaCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.UrbanDictionaryCommand());
messageCommandListener.registerCommand(new MessageHelloCommand());
messageCommandListener.registerCommand(context.getBean(MessageAliasCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageAvatarCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageBanCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageBannerCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageBotInfoCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageCoinFlipCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageClearCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageDiceRollCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageHelpCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageInviteCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageKickCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageLoveCalculatorCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageMagicBallCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageSayCommand.class));
messageCommandListener.registerCommand(context.getBean(MessageTimeoutCommand.class));
messageCommandListener.registerCommand(new MessageTriviaCommand());
messageCommandListener.registerCommand(new MessageUrbanDictionaryCommand());
// register listeners
Cache.setSlashCommandListener(slashCommandListener);
@@ -182,41 +202,28 @@ public class HidekoBot
jda.addEventListener(messageCommandListener);
jda.addEventListener(slashCommandListener);
jda.addEventListener(slashCommandCompletionListener);
jda.addEventListener(new ButtonInteractionListener());
jda.addEventListener(new SelectMenuInteractionListener());
jda.addEventListener(buttonInteractionListener);
jda.addEventListener(selectMenuInteractionListener);
// update slash commands (delayed)
final boolean finalForceUpdateCommands = forceUpdateCommands;
try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor()) {
executor.schedule(() -> CommandUtil.updateSlashCommands(finalForceUpdateCommands),
try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor())
{
executor.schedule(() -> commandService.updateSlashCommands(finalForceUpdateCommands),
1, TimeUnit.SECONDS);
}
// set the bot's status
jda.getPresence().setStatus(OnlineStatus.ONLINE);
// connect to database
LOGGER.info("Connecting to database...");
String dbFilePath = Cache.getExecPath() + File.separator + "db.sqlite"; // in current directory
DatabaseSource databaseSource = new DatabaseSource(dbFilePath);
if(databaseSource.connect() && databaseSource.initDb())
{
LOGGER.info("Database connection initialized!");
Cache.setDatabaseSourceInstance(databaseSource);
// load data here...
LOGGER.info("Database data loaded into memory!");
} else {
LOGGER.error("Error initializing database connection!");
}
// start scheduled runnables
ScheduledExecutorService scheduler = Cache.getTaskScheduler();
ExpiredMessageTask expiredMessageTask = new ExpiredMessageTask();
ExpiredMessageTask expiredMessageTask = new ExpiredMessageTask(services.databaseService(), services.commandService());
scheduler.scheduleAtFixedRate(expiredMessageTask, 5L, 5L, TimeUnit.SECONDS); //every 5 seconds
HeartBeatTask heartBeatTask = new HeartBeatTask();
scheduler.scheduleAtFixedRate(heartBeatTask, 10L, 30L, TimeUnit.SECONDS); //every 30 seconds
StatusUpdateTask statusUpdateTask = new StatusUpdateTask();
scheduler.scheduleAtFixedRate(statusUpdateTask, 0L, 60L * 5L, TimeUnit.SECONDS); // every 5 minutes
if (enableRandomSeedUpdaterTask)
@@ -240,6 +247,7 @@ public class HidekoBot
LOGGER.info("Invite Link: {}", Cache.getInviteUrl());
}
public static JDA getAPI()
{
return jda;

View File

@@ -1,16 +1,15 @@
package wtf.beatrice.hidekobot.commands.base;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.LinkedList;
@Component
public class Alias
{
private Alias() {
throw new IllegalStateException("Utility class");
}
public static String generateNiceAliases(MessageCommand command)
public String generateNiceAliases(MessageCommand command)
{
LinkedList<String> aliases = command.getCommandLabels();
StringBuilder aliasesStringBuilder = new StringBuilder();

View File

@@ -2,6 +2,7 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.util.FormatUtil;
@@ -11,13 +12,11 @@ import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
import java.util.List;
@Component
public class BotInfo
{
private BotInfo() {
throw new IllegalStateException("Utility class");
}
public static MessageEmbed generateEmbed(List<String> commandLabels)
public MessageEmbed generateEmbed(List<String> commandLabels)
{
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
@@ -57,7 +56,8 @@ public class BotInfo
String messageCommandsInfo;
if (Cache.getMessageCommandListener() == null)
messageCommandsInfo = "❌ disabled";
else {
else
{
messageCommandsInfo = "✅ available";
commandsCount += Cache.getMessageCommandListener().getRegisteredCommands().size();
}
@@ -67,7 +67,8 @@ public class BotInfo
String slashCommandsInfo;
if (Cache.getMessageCommandListener() == null)
slashCommandsInfo = "❌ disabled";
else {
else
{
slashCommandsInfo = "✅ available";
commandsCount += Cache.getSlashCommandListener().getRegisteredCommands().size();
}
@@ -78,7 +79,8 @@ public class BotInfo
if (RandomUtil.isRandomOrgKeyValid())
{
randomOrgInfo = "✅ connected";
} else {
} else
{
randomOrgInfo = "❌ disabled";
}
embedBuilder.addField("Random.org", randomOrgInfo, true);

View File

@@ -8,45 +8,50 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class ClearChat
{
private ClearChat() {
throw new IllegalStateException("Utility class");
}
public static String getLabel() {
public String getLabel()
{
return "clear";
}
public static String getDescription() {
public String getDescription()
{
return "Clear the current channel's chat.";
}
public static Permission getPermission() {
public Permission getPermission()
{
return Permission.MESSAGE_MANAGE;
}
public static String checkDMs(Channel channel)
public String checkDMs(Channel channel)
{
if (!(channel instanceof TextChannel))
{ return "\uD83D\uDE22 Sorry! I can't delete messages here."; }
{
return "\uD83D\uDE22 Sorry! I can't delete messages here.";
}
return null;
}
public static String checkDeleteAmount(int toDeleteAmount)
public String checkDeleteAmount(int toDeleteAmount)
{
if (toDeleteAmount <= 0)
{ return "\uD83D\uDE22 Sorry, I can't delete that amount of messages!"; }
{
return "\uD83D\uDE22 Sorry, I can't delete that amount of messages!";
}
return null;
}
public static int delete(int toDeleteAmount,
public int delete(int toDeleteAmount,
long startingMessageId,
MessageChannel channel)
{
@@ -82,7 +87,8 @@ public class ClearChat
int iterationSize = limit;
// if we are at the last iteration... check if we have <limit> or fewer messages to delete
if(iteration+1 == iterations && remainder != 0) {
if (iteration + 1 == iterations && remainder != 0)
{
iterationSize = remainder;
}
@@ -95,7 +101,8 @@ public class ClearChat
else outOfBounds = true;
// increase deleted counter by 1
deleted++;
} else {
} else
{
// get the last <iterationSize - 1> messages.
MessageHistory.MessageRetrieveAction action = channel.getHistoryBefore(messageId, iterationSize - 1);
// note: first one is the most recent, last one is the oldest message.
@@ -108,7 +115,8 @@ public class ClearChat
if (messages.size() <= 1)
{
outOfBounds = true;
} else {
} else
{
// before deleting, we need to grab the <previous to the oldest> message's id for next iteration.
action = channel.getHistoryBefore(messages.getLast().getIdLong(), 1);
@@ -123,10 +131,10 @@ public class ClearChat
if (messages.size() == 1)
{
messages.getFirst().delete().queue();
}
else if(!messages.isEmpty())
} else if (!messages.isEmpty())
{
try
{
try {
((TextChannel) channel).deleteMessages(messages).complete();
/* alternatively, we could use purgeMessages, which is smarter...
however, it also tries to delete messages older than 2 weeks
@@ -147,13 +155,13 @@ public class ClearChat
return deleted;
}
public static Button getDismissButton()
public Button getDismissButton()
{
return Button.primary("generic_dismiss", "Dismiss")
.withEmoji(Emoji.fromUnicode(""));
}
public static String parseAmount(int deleted)
public String parseAmount(int deleted)
{
if (deleted < 1)
@@ -162,12 +170,16 @@ public class ClearChat
} else if (deleted == 1)
{
return "✂ Cleared 1 message!";
} else {
} else
{
return "✂ Cleared " + deleted + " messages!";
}
}
// cap the amount to avoid abuse.
public static int getMaxAmount() { return 1000; }
public int getMaxAmount()
{
return 1000;
}
}

View File

@@ -6,24 +6,23 @@ import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.ArrayList;
import java.util.List;
@Component
public class CoinFlip
{
private CoinFlip() {
throw new IllegalStateException("Utility class");
}
public static Button getReflipButton() {
public Button getReflipButton()
{
return Button.primary("coinflip_reflip", "Flip again")
.withEmoji(Emoji.fromUnicode("\uD83E\uDE99"));
}
public static String genRandom()
public String genRandom()
{
int rand = RandomUtil.getRandomNumber(0, 1);
String msg;
@@ -31,40 +30,61 @@ public class CoinFlip
if (rand == 1)
{
msg = ":coin: It's **Heads**!";
} else {
} else
{
msg = "It's **Tails**! :coin:";
}
return msg;
}
public static void buttonReFlip(ButtonInteractionEvent event)
public void buttonReFlip(ButtonInteractionEvent event)
{
// check if the user interacting is the same one who ran the command
if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId())))
// Ack ASAP to avoid 3s timeout
event.deferEdit().queue(hook -> {
// Permission check **after** ack
if (!Cache.getServices().databaseService().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))
{
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
hook.sendMessage("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// set old message's button as disabled
List<ActionRow> actionRows = event.getMessage().getActionRows();
actionRows.set(0, actionRows.get(0).asDisabled());
event.editComponents(actionRows).queue();
// Disable all components on the original message
List<ActionRow> oldRows = event.getMessage().getActionRows();
List<ActionRow> disabledRows = new ArrayList<>(oldRows.size());
for (ActionRow row : oldRows)
{
disabledRows.add(row.asDisabled());
}
hook.editOriginalComponents(disabledRows).queue();
// perform coin flip
event.getHook().sendMessage(genRandom())
// Send a follow-up with a fresh button
hook.sendMessage(genRandom())
.addActionRow(getReflipButton())
.queue((message) ->
.queue(msg -> trackAndRestrict(msg, event.getUser()), err -> {
});
}, failure -> {
// Rare: if we couldn't ack, try best-effort fallbacks
try
{
List<ActionRow> oldRows = event.getMessage().getActionRows();
List<ActionRow> disabledRows = new ArrayList<>(oldRows.size());
for (ActionRow row : oldRows) disabledRows.add(row.asDisabled());
event.getMessage().editMessageComponents(disabledRows).queue();
} catch (Exception ignored)
{
// set the command as expiring and restrict it to the user who ran it
trackAndRestrict(message, event.getUser());
}, (error) -> {});
}
public static void trackAndRestrict(Message replyMessage, User user)
event.getChannel().sendMessage(genRandom())
.addActionRow(getReflipButton())
.queue(msg -> trackAndRestrict(msg, event.getUser()), err -> {
});
});
}
public void trackAndRestrict(Message replyMessage, User user)
{
Cache.getDatabaseSource().queueDisabling(replyMessage);
Cache.getDatabaseSource().trackRanCommandReply(replyMessage, user);
Cache.getServices().databaseService().queueDisabling(replyMessage);
Cache.getServices().databaseService().trackRanCommandReply(replyMessage, user);
}
}

View File

@@ -2,6 +2,7 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.User;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.fun.Dice;
@@ -12,14 +13,11 @@ import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
@Component
public class DiceRoll
{
private DiceRoll() {
throw new IllegalStateException("Utility class");
}
public static MessageResponse buildResponse(User author, String[] args)
public MessageResponse buildResponse(User author, String[] args)
{
LinkedHashMap<Dice, Integer> dicesToRoll = new LinkedHashMap<>();
String diceRegex = "d\\d+";
@@ -44,7 +42,8 @@ public class DiceRoll
if (currentDice == null)
{
currentDice = new Dice(6);
} else {
} else
{
currentDice = new Dice(currentDice);
}
@@ -56,8 +55,7 @@ public class DiceRoll
lastPushedDice = currentDice.getUUID();
dicesToRoll.put(currentDice, currentAmount);
totalRolls += currentAmount;
}
else if(arg.matches(diceRegex))
} else if (arg.matches(diceRegex))
{
int sides = Integer.parseInt(arg.substring(1));
@@ -128,7 +126,7 @@ public class DiceRoll
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setAuthor(author.getName(), null, author.getAvatarUrl());
embedBuilder.setTitle("Dice Roll");
if (RandomUtil.isRandomOrgKeyValid())
@@ -138,13 +136,16 @@ public class DiceRoll
int total = 0;
int previousDiceSides = 0;
for (Dice dice : rolledDices) {
for (Dice dice : rolledDices)
{
int diceSize = dice.getSides();
if (previousDiceSides != diceSize) {
if (previousDiceSides != diceSize)
{
message.append("\nd").append(diceSize).append(": ");
previousDiceSides = diceSize;
} else if (previousDiceSides != 0) {
} else if (previousDiceSides != 0)
{
message.append(", ");
}

View File

@@ -4,17 +4,15 @@ import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
@Component
public class Invite
{
private Invite() {
throw new IllegalStateException("Utility class");
}
public static MessageEmbed generateEmbed()
public MessageEmbed generateEmbed()
{
EmbedBuilder embedBuilder = new EmbedBuilder();
@@ -32,7 +30,7 @@ public class Invite
return embedBuilder.build();
}
public static Button getInviteButton()
public Button getInviteButton()
{
String inviteUrl = Cache.getInviteUrl();
return Button.link(inviteUrl, "Invite " + Cache.getBotName())

View File

@@ -3,19 +3,17 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.concurrent.TimeUnit;
@Component
public class LoveCalculator
{
private LoveCalculator() {
throw new IllegalStateException("Utility class");
}
public static MessageEmbed buildEmbedAndCacheResult(User author, User user1, User user2)
public MessageEmbed buildEmbedAndCacheResult(User author, User user1, User user2)
{
String userId1 = user1.getId();
String userId2 = user2.getId();
@@ -37,7 +35,7 @@ public class LoveCalculator
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setAuthor(author.getName(), null, author.getAvatarUrl());
embedBuilder.setTitle("Love Calculator");
embedBuilder.addField("\uD83D\uDC65 People",

View File

@@ -3,6 +3,7 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.util.RandomUtil;
@@ -11,19 +12,16 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@Component
public class MagicBall
{
private MagicBall() {
throw new IllegalStateException("Utility class");
}
public static LinkedList<String> getLabels()
public LinkedList<String> getLabels()
{
return new LinkedList<>(Arrays.asList("8ball", "8b", "eightball", "magicball"));
}
private static final List<String> answers = new ArrayList<>(
private final List<String> answers = new ArrayList<>(
Arrays.asList("It is certain.",
"It is decidedly so.",
"Without a doubt.",
@@ -45,13 +43,13 @@ public class MagicBall
"Outlook not so good.",
"Very doubtful."));
public static String getRandomAnswer()
public String getRandomAnswer()
{
int answerPos = RandomUtil.getRandomNumber(0, answers.size() - 1);
return answers.get(answerPos);
}
public static MessageEmbed generateEmbed(String question, User author)
public MessageEmbed generateEmbed(String question, User author)
{
// add a question mark at the end, if missing.
// this might not always apply but it's fun
@@ -60,7 +58,7 @@ public class MagicBall
String answer = getRandomAnswer();
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setAuthor(author.getName(), null, author.getAvatarUrl());
embedBuilder.setTitle("Magic Ball");
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.addField("❓ Question", question, false);

View File

@@ -3,26 +3,26 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.utils.ImageProxy;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.MessageResponse;
@Component
public class ProfileImage
{
private ProfileImage() {
throw new IllegalStateException("Utility class");
}
public static int parseResolution(int resolution)
public int parseResolution(int resolution)
{
int[] acceptedSizes = Cache.getSupportedAvatarResolutions();
// method to find closest value to accepted values
int distance = Math.abs(acceptedSizes[0] - resolution);
int idx = 0;
for(int c = 1; c < acceptedSizes.length; c++){
for (int c = 1; c < acceptedSizes.length; c++)
{
int cdistance = Math.abs(acceptedSizes[c] - resolution);
if(cdistance < distance){
if (cdistance < distance)
{
idx = c;
distance = cdistance;
}
@@ -31,7 +31,7 @@ public class ProfileImage
return acceptedSizes[idx];
}
public static MessageResponse buildResponse(int resolution, User user, ImageType imageType)
public MessageResponse buildResponse(int resolution, User user, ImageType imageType)
{
String imageTypeName = imageType.name().toLowerCase();
String resolutionString;
@@ -44,7 +44,8 @@ public class ProfileImage
{
resolutionString = resolution + " × " + resolution;
imageLink = user.getEffectiveAvatar().getUrl(resolution);
} else {
} else
{
int verticalRes = 361 * resolution / 1024;
resolutionString = resolution + " × " + verticalRes;
if (bannerProxy != null)
@@ -72,7 +73,8 @@ public class ProfileImage
if (imageType == ImageType.AVATAR)
{
currLink = user.getEffectiveAvatar().getUrl(currSize);
} else {
} else
{
if (bannerProxy == null) break;
currLink = bannerProxy.getUrl(currSize);
}
@@ -87,15 +89,16 @@ public class ProfileImage
embedBuilder.addField("Available resolutions", links.toString(), false);
if (imageLink != null)
embedBuilder.setImage(imageLink);
if(imageLink == null) {
if (imageLink == null)
{
String error = "I couldn't find " + user.getAsMention() + "'s " + imageTypeName + "!";
return new MessageResponse(error, null);
} else {
} else
{
return new MessageResponse(null, embedBuilder.build());
}
}

View File

@@ -1,15 +1,13 @@
package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.Permission;
import org.springframework.stereotype.Component;
@Component
public class Say
{
private Say() {
throw new IllegalStateException("Utility class");
}
public static Permission getPermission() {
public Permission getPermission()
{
return Permission.MESSAGE_MANAGE;
}
}

View File

@@ -19,7 +19,6 @@ import wtf.beatrice.hidekobot.objects.fun.TriviaCategory;
import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion;
import wtf.beatrice.hidekobot.objects.fun.TriviaScore;
import wtf.beatrice.hidekobot.runnables.TriviaTask;
import wtf.beatrice.hidekobot.util.CommandUtil;
import java.io.BufferedReader;
import java.io.IOException;
@@ -27,16 +26,18 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class Trivia
{
private Trivia() {
private Trivia()
{
throw new IllegalStateException("Utility class");
}
@@ -44,22 +45,31 @@ public class Trivia
private static final String TRIVIA_API_LINK = "https://opentdb.com/api.php?amount=10&type=multiple&category=";
private static final String TRIVIA_API_CATEGORIES_LINK = "https://opentdb.com/api_category.php";
public static List<String> channelsRunningTrivia = new ArrayList<>();
public static List<String> channelsRunningTrivia = Collections.synchronizedList(new ArrayList<>());
// first string is the channelId, the list contain all users who responded there
public static HashMap<String, List<String>> channelAndWhoResponded = new HashMap<>();
public static ConcurrentHashMap<String, List<String>> channelAndWhoResponded = new ConcurrentHashMap<>();
// first string is the channelId, the list contain all score records for that channel
public static HashMap<String, LinkedList<TriviaScore>> channelAndScores = new HashMap<>();
public static ConcurrentHashMap<String, LinkedList<TriviaScore>> channelAndScores = new ConcurrentHashMap<>();
public static String getTriviaLink(int categoryId) {return TRIVIA_API_LINK + categoryId; }
public static String getCategoriesLink() {return TRIVIA_API_CATEGORIES_LINK; }
public static String getTriviaLink(int categoryId)
{
return TRIVIA_API_LINK + categoryId;
}
public static String getNoDMsError() {
public static String getCategoriesLink()
{
return TRIVIA_API_CATEGORIES_LINK;
}
public static String getNoDMsError()
{
return "\uD83D\uDE22 Sorry! Trivia doesn't work in DMs.";
}
public static String getTriviaAlreadyRunningError() {
public static String getTriviaAlreadyRunningError()
{
// todo nicer looking
return "Trivia is already running here!";
}
@@ -102,7 +112,8 @@ public class Trivia
public static JSONObject fetchJson(String link)
{
try {
try
{
URL url = new URL(link);
URLConnection connection = url.openConnection();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
@@ -115,7 +126,8 @@ public class Trivia
}
bufferedReader.close();
return new JSONObject(jsonStrBuilder.toString());
} catch (IOException e) {
} catch (IOException e)
{
LOGGER.error("JSON Parsing Exception", e);
}
@@ -169,6 +181,8 @@ public class Trivia
public static void handleAnswer(ButtonInteractionEvent event, AnswerType answerType)
{
// Ack immediately with an ephemeral deferral to avoid 3s timeout
event.deferReply(true).queue(hook -> {
User user = event.getUser();
String channelId = event.getChannel().getId();
@@ -176,6 +190,7 @@ public class Trivia
{
LinkedList<TriviaScore> scores = channelAndScores.get(channelId);
if (scores == null) scores = new LinkedList<>();
TriviaScore currentUserScore = null;
for (TriviaScore score : scores)
{
@@ -194,26 +209,35 @@ public class Trivia
if (answerType.equals(AnswerType.CORRECT))
{
event.reply(user.getAsMention() + " got it right! \uD83E\uDD73 (**+3**)").queue();
// Public message in channel
event.getChannel().sendMessage(user.getAsMention() + " got it right! \uD83E\uDD73 (**+3**)").queue();
currentUserScore.changeScore(3);
} else {
event.reply("" + user.getAsMention() + ", that's not the right answer! (**-1**)").queue();
} else
{
event.getChannel().sendMessage("" + user.getAsMention() + ", that's not the right answer! (**-1**)").queue();
currentUserScore.changeScore(-1);
}
scores.add(currentUserScore);
channelAndScores.put(channelId, scores);
} else {
event.reply("☹️ " + user.getAsMention() + ", you can't answer twice!")
.queue(interaction ->
Cache.getTaskScheduler().schedule(() ->
interaction.deleteOriginal().queue(), 3, TimeUnit.SECONDS));
}
} else
{
// Show the warning **in the original ephemeral message**, then delete it after 5s.
hook.editOriginal("☹️ " + user.getAsMention() + ", you can't answer twice!").queue(v ->
hook.deleteOriginal().queueAfter(3, TimeUnit.SECONDS, null, __ -> {
})
);
return; // don't run the generic cleanup below; we want the message visible for ~5s
}
private static boolean trackResponse(User user, MessageChannel channel)
// Clean up the ephemeral deferral (no visible ephemeral message left) for the normal path
hook.deleteOriginal().queue(null, __ -> {
});
}, __ -> {
});
}
private static synchronized boolean trackResponse(User user, MessageChannel channel)
{
String userId = user.getId();
String channelId = channel.getId();
@@ -230,22 +254,25 @@ public class Trivia
responders.add(userId);
channelAndWhoResponded.put(channelId, responders);
return true; // response was successfully tracked
} else {
} else
{
return false; // response wasn't tracked because there already was an entry
}
}
public static void handleMenuSelection(StringSelectInteractionEvent event)
{
// Ack immediately (ephemeral) so we can safely do DB/work
event.deferReply(true).queue(hook -> {
// check if the user interacting is the same one who ran the command
if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId())))
if (!(Cache.getServices().databaseService().isUserTrackedFor(event.getUser().getId(), event.getMessageId())))
{
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
hook.sendMessage("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// todo: we shouldn't use this method, since it messes with the database... look at coin reflip
CommandUtil.disableExpired(event.getMessageId());
// Disable buttons on the original message via service (uses separate REST calls)
Cache.getServices().commandService().disableExpired(event.getMessageId());
SelectOption pickedOption = event.getInteraction().getSelectedOptions().get(0);
String categoryName = pickedOption.getLabel();
@@ -255,6 +282,12 @@ public class Trivia
TriviaCategory category = new TriviaCategory(categoryName, categoryId);
startTrivia(event, category);
// remove the ephemeral deferral to keep things clean
hook.deleteOriginal().queue(null, __ -> {
});
}, __ -> {
});
}
public static void startTrivia(StringSelectInteractionEvent event, TriviaCategory category)
@@ -265,19 +298,19 @@ public class Trivia
if (Trivia.channelsRunningTrivia.contains(channel.getId()))
{
// todo nicer looking
// todo: also what if the bot stops (database...?)
// todo: also what if the message is already deleted
Message err = event.reply("Trivia is already running here!").complete().retrieveOriginal().complete();
Cache.getTaskScheduler().schedule(() -> err.delete().queue(), 10, TimeUnit.SECONDS);
// Already running: inform ephemerally via hook (the interaction was deferred in the caller)
event.getHook().sendMessage(Trivia.getTriviaAlreadyRunningError())
.setEphemeral(true)
.queue(msg -> Cache.getTaskScheduler().schedule(() -> msg.delete().queue(), 10, TimeUnit.SECONDS));
return;
} else {
// todo nicer looking
event.reply("Starting new Trivia session!").queue();
} else
{
// Public info that a new session is starting
channel.sendMessage("Starting new Trivia session!").queue();
}
TriviaTask triviaTask = new TriviaTask(author, channel, category);
TriviaTask triviaTask = new TriviaTask(author, channel, category,
Cache.getServices().databaseService(), Cache.getServices().commandService());
ScheduledFuture<?> future =
Cache.getTaskScheduler().scheduleAtFixedRate(triviaTask,
0,
@@ -288,7 +321,8 @@ public class Trivia
Trivia.channelsRunningTrivia.add(channel.getId());
}
public enum AnswerType {
public enum AnswerType
{
CORRECT, WRONG
}

View File

@@ -14,7 +14,7 @@ import org.apache.commons.text.WordUtils;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.services.DatabaseService;
import wtf.beatrice.hidekobot.util.SerializationUtil;
import java.util.ArrayList;
@@ -25,15 +25,19 @@ import java.util.List;
public class UrbanDictionary
{
private UrbanDictionary() {
private UrbanDictionary()
{
throw new IllegalStateException("Utility class");
}
public static LinkedList<String> getCommandLabels()
{ return new LinkedList<>(Arrays.asList("urban", "urbandictionary", "ud")); }
{
return new LinkedList<>(Arrays.asList("urban", "urbandictionary", "ud"));
}
public static String getBaseUrl() {
public static String getBaseUrl()
{
return "https://www.urbandictionary.com/define.php?term=";
}
@@ -55,7 +59,8 @@ public class UrbanDictionary
.withEmoji(Emoji.fromFormatted("\uD83D\uDDD1"));
}
public static String getNoArgsError() {
public static String getNoArgsError()
{
return "\uD83D\uDE22 I need to know what to search for!";
}
@@ -83,7 +88,7 @@ public class UrbanDictionary
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle(term + ", on Urban Dictionary", url);
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setAuthor(author.getName(), null, author.getAvatarUrl());
embedBuilder.addField("\uD83D\uDCD6 Definition", search.getPlaintextMeanings().get(page), false);
embedBuilder.addField("\uD83D\uDCAD Example", search.getPlaintextExamples().get(page), false);
embedBuilder.addField("\uD83D\uDCCC Submission",
@@ -102,9 +107,9 @@ public class UrbanDictionary
public static void track(Message message, User user, UrbanSearch search, String sanitizedTerm)
{
Cache.getDatabaseSource().queueDisabling(message);
Cache.getDatabaseSource().trackRanCommandReply(message, user);
Cache.getDatabaseSource().trackUrban(search.getSerializedMeanings(),
Cache.getServices().databaseService().queueDisabling(message);
Cache.getServices().databaseService().trackRanCommandReply(message, user);
Cache.getServices().databaseService().trackUrban(search.getSerializedMeanings(),
search.getSerializedExamples(),
search.getSerializedContributors(),
search.getSerializedDates(),
@@ -114,17 +119,19 @@ public class UrbanDictionary
public static void changePage(ButtonInteractionEvent event, ChangeType changeType)
{
event.deferEdit().queue();
String messageId = event.getMessageId();
DatabaseSource database = Cache.getDatabaseSource();
DatabaseService database = Cache.getServices().databaseService();
// check if the user interacting is the same one who ran the command
if (!(database.isUserTrackedFor(event.getUser().getId(), messageId))) {
if (!(database.isUserTrackedFor(event.getUser().getId(), messageId)))
{
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// get current page and calculate how many pages there are
int page = Cache.getDatabaseSource().getUrbanPage(messageId);
int page = database.getUrbanPage(messageId);
String term = database.getUrbanTerm(messageId);
String url = generateUrl(term);
@@ -156,14 +163,16 @@ public class UrbanDictionary
if (page > 0)
{
components.add(UrbanDictionary.getPreviousPageButton().asEnabled());
} else {
} else
{
components.add(UrbanDictionary.getPreviousPageButton().asDisabled());
}
if (page + 1 == search.getPages())
{
components.add(UrbanDictionary.getNextPageButton().asDisabled());
} else {
} else
{
components.add(UrbanDictionary.getNextPageButton().asEnabled());
}
@@ -172,7 +181,9 @@ public class UrbanDictionary
ActionRow currentRow = ActionRow.of(components);
// update the message
event.editComponents(currentRow).setEmbeds(updatedEmbed).queue();
event.getHook().editOriginalEmbeds(updatedEmbed)
.setComponents(currentRow)
.queue();
database.setUrbanPage(messageId, page);
database.resetExpiryTimestamp(messageId);
}
@@ -282,39 +293,48 @@ public class UrbanDictionary
pages = submissionDates.size();
}
public List<String> getPlaintextMeanings() {
public List<String> getPlaintextMeanings()
{
return this.plaintextMeanings;
}
public List<String> getPlaintextExamples() {
public List<String> getPlaintextExamples()
{
return this.plaintextExamples;
}
public List<String> getContributorsNames() {
public List<String> getContributorsNames()
{
return this.contributorsNames;
}
public List<String> getSubmissionDates() {
public List<String> getSubmissionDates()
{
return this.submissionDates;
}
public String getSerializedMeanings() {
public String getSerializedMeanings()
{
return serializedMeanings;
}
public String getSerializedExamples() {
public String getSerializedExamples()
{
return serializedExamples;
}
public String getSerializedContributors() {
public String getSerializedContributors()
{
return serializedContributors;
}
public String getSerializedDates() {
public String getSerializedDates()
{
return serializedDates;
}
public int getPages() {
public int getPages()
{
return pages;
}
}

View File

@@ -12,6 +12,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.objects.MessageResponse;
@@ -24,17 +25,14 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
public class UserPunishment
{
private UserPunishment() {
throw new IllegalStateException("Utility class");
}
private static final Duration maxTimeoutDuration = Duration.of(28, ChronoUnit.DAYS);
private static final Duration minTimeoutDuration = Duration.of(30, ChronoUnit.SECONDS);
public static void handle(SlashCommandInteractionEvent event, PunishmentType punishmentType)
public void handle(SlashCommandInteractionEvent event, PunishmentType punishmentType)
{
// this might take a sec
event.deferReply().queue();
@@ -91,7 +89,7 @@ public class UserPunishment
event.getHook().editOriginal(response.content()).queue();
}
public static void handle(MessageReceivedEvent event, String[] args, PunishmentType punishmentType)
public void handle(MessageReceivedEvent event, String[] args, PunishmentType punishmentType)
{
Mentions msgMentions = event.getMessage().getMentions();
List<IMentionable> mentions = msgMentions.getMentions();
@@ -108,7 +106,7 @@ public class UserPunishment
event.getMessage().reply(response.content()).queue();
}
public static MessageResponse getResponse(User author,
public MessageResponse getResponse(User author,
PunishmentType punishmentType,
MessageChannelUnion channel,
List<IMentionable> mentions,
@@ -132,7 +130,8 @@ public class UserPunishment
String mentionedId = mentions.get(0).getId();
User mentioned = null;
try {
try
{
mentioned = HidekoBot.getAPI().retrieveUserById(mentionedId).complete();
} catch (RuntimeException ignored)
{
@@ -172,16 +171,22 @@ public class UserPunishment
AuditableRestAction<Void> punishmentAction = null;
boolean impossible = false;
try {
switch (punishmentType) {
try
{
switch (punishmentType)
{
case BAN -> punishmentAction = guild.ban(mentioned, 0, TimeUnit.SECONDS);
case KICK -> punishmentAction = guild.kick(mentioned);
case TIMEOUT -> {
if(args != null)
case TIMEOUT ->
{
// Ensure a duration argument is provided at index 1 (after mention/user)
if (args == null || args.length <= 1)
{
return new MessageResponse("Please specify a punishment duration!", null);
}
String durationStr = args[1];
duration = FormatUtil.parseDuration(durationStr);
}
boolean isDurationValid = true;
@@ -192,7 +197,7 @@ public class UserPunishment
if (minTimeoutDuration.compareTo(duration) > 0) isDurationValid = false;
}
if(duration == null || !isDurationValid)
if (!isDurationValid)
{
// todo nicer looking with emojis
return new MessageResponse("Sorry, but the specified duration is invalid!", null);
@@ -201,7 +206,8 @@ public class UserPunishment
punishmentAction = guild.timeoutFor(mentioned, duration);
}
}
} catch (RuntimeException ignored) {
} catch (RuntimeException ignored)
{
impossible = true;
}
@@ -216,9 +222,10 @@ public class UserPunishment
}
if (!reason.isEmpty() && !reasonBuilder.isEmpty())
punishmentAction.reason("[" + author.getAsTag() + "] " + reason);
punishmentAction.reason("[" + author.getName() + "] " + reason);
try {
try
{
punishmentAction.complete();
} catch (RuntimeException ignored)
{
@@ -229,7 +236,7 @@ public class UserPunishment
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setAuthor(author.getName(), null, author.getAvatarUrl());
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("User " + punishmentType.getPastTense());
@@ -248,7 +255,8 @@ public class UserPunishment
}
public enum PunishmentType {
public enum PunishmentType
{
KICK("kicked"),
BAN("banned"),
TIMEOUT("timed out"),

View File

@@ -13,12 +13,14 @@ import java.util.List;
public class ProfileImageCommandCompleter extends SlashArgumentsCompleterImpl
{
public ProfileImageCommandCompleter(SlashCommand parentCommand) {
public ProfileImageCommandCompleter(SlashCommand parentCommand)
{
super(parentCommand);
}
@Override
public void runCompletion(@NotNull CommandAutoCompleteInteractionEvent event) {
public void runCompletion(@NotNull CommandAutoCompleteInteractionEvent event)
{
if (event.getFocusedOption().getName().equals("size"))
{

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.Alias;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -13,40 +15,54 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class AliasCommand implements MessageCommand
@Component
public class MessageAliasCommand implements MessageCommand
{
private final Alias alias;
public MessageAliasCommand(@Autowired Alias alias)
{
this.alias = alias;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("alias", "aliases"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "See other command aliases.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<command>";
}
@@ -67,7 +83,7 @@ public class AliasCommand implements MessageCommand
return;
}
String aliases = Alias.generateNiceAliases(command);
String aliases = alias.generateNiceAliases(command);
aliases = "Aliases for **" + command.getCommandLabels().get(0) + "**: " + aliases;
event.getMessage()

View File

@@ -6,6 +6,8 @@ import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.commands.base.ProfileImage;
import wtf.beatrice.hidekobot.objects.MessageResponse;
@@ -16,40 +18,53 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class AvatarCommand implements MessageCommand
@Component
public class MessageAvatarCommand implements MessageCommand
{
private final ProfileImage profileImage;
public MessageAvatarCommand(@Autowired ProfileImage profileImage)
{
this.profileImage = profileImage;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("avatar"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get someone's avatar, or your own. You can additionally specify a resolution.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "[mentioned user] [resolution]";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}
@@ -63,19 +78,22 @@ public class AvatarCommand implements MessageCommand
// (mentions are handled differently by a specific method)
boolean resFound = false;
for (String arg : args) {
try {
for (String arg : args)
{
try
{
int givenRes = Integer.parseInt(arg);
resolution = ProfileImage.parseResolution(givenRes);
resolution = profileImage.parseResolution(givenRes);
resFound = true;
break;
} catch (NumberFormatException ignored) {
} catch (NumberFormatException ignored)
{
// ignored because we're running a check after this block
}
}
// fallback in case we didn't find any specified resolution
if(!resFound) resolution = ProfileImage.parseResolution(512);
if (!resFound) resolution = profileImage.parseResolution(512);
// check if someone is mentioned
Mentions mentions = event.getMessage().getMentions();
@@ -92,7 +110,7 @@ public class AvatarCommand implements MessageCommand
if (user == null) user = event.getAuthor();
// send a response
MessageResponse response = ProfileImage.buildResponse(resolution, user, ProfileImage.ImageType.AVATAR);
MessageResponse response = profileImage.buildResponse(resolution, user, ProfileImage.ImageType.AVATAR);
if (response.content() != null)
{
event.getMessage().reply(response.content()).queue();

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -13,46 +15,59 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class BanCommand implements MessageCommand
@Component
public class MessageBanCommand implements MessageCommand
{
private final UserPunishment userPunishment;
public MessageBanCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("ban"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return new ArrayList<Permission>(Collections.singletonList(Permission.BAN_MEMBERS));
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.MODERATION;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Ban the mentioned user.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<mentioned user> [reason]";
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
UserPunishment.handle(event, args, UserPunishment.PunishmentType.BAN);
userPunishment.handle(event, args, UserPunishment.PunishmentType.BAN);
}
}

View File

@@ -6,6 +6,8 @@ import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.commands.base.ProfileImage;
import wtf.beatrice.hidekobot.objects.MessageResponse;
@@ -16,40 +18,53 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class BannerCommand implements MessageCommand
@Component
public class MessageBannerCommand implements MessageCommand
{
private final ProfileImage profileImage;
public MessageBannerCommand(@Autowired ProfileImage profileImage)
{
this.profileImage = profileImage;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("banner"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get someone's profile banner, or your own.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "[mentioned user] [resolution]";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}
@@ -63,19 +78,22 @@ public class BannerCommand implements MessageCommand
// (mentions are handled differently by a specific method)
boolean resFound = false;
for (String arg : args) {
try {
for (String arg : args)
{
try
{
int givenRes = Integer.parseInt(arg);
resolution = ProfileImage.parseResolution(givenRes);
resolution = profileImage.parseResolution(givenRes);
resFound = true;
break;
} catch (NumberFormatException ignored) {
} catch (NumberFormatException ignored)
{
// ignored because we're running a check after this block
}
}
// fallback in case we didn't find any specified resolution
if(!resFound) resolution = ProfileImage.parseResolution(512);
if (!resFound) resolution = profileImage.parseResolution(512);
// check if someone is mentioned
Mentions mentions = event.getMessage().getMentions();
@@ -92,7 +110,7 @@ public class BannerCommand implements MessageCommand
if (user == null) user = event.getAuthor();
// send a response
MessageResponse response = ProfileImage.buildResponse(resolution, user, ProfileImage.ImageType.BANNER);
MessageResponse response = profileImage.buildResponse(resolution, user, ProfileImage.ImageType.BANNER);
if (response.content() != null)
{
event.getMessage().reply(response.content()).queue();

View File

@@ -5,6 +5,8 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.BotInfo;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -14,55 +16,71 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class BotInfoCommand implements MessageCommand
@Component
public class MessageBotInfoCommand implements MessageCommand
{
private final BotInfo botInfo;
public MessageBotInfoCommand(@Autowired BotInfo botInfo)
{
this.botInfo = botInfo;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("botinfo", "info"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get general info about the bot.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) {
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
// get a list of message commands
LinkedList<MessageCommand> messageCommands = Cache.getMessageCommandListener().getRegisteredCommands();
LinkedList<String> commandNames = new LinkedList<>();
for (MessageCommand command : messageCommands) {
for (MessageCommand command : messageCommands)
{
commandNames.add(command.getCommandLabels().get(0));
}
// send the list
MessageEmbed embed = BotInfo.generateEmbed(commandNames);
MessageEmbed embed = botInfo.generateEmbed(commandNames);
event.getMessage().replyEmbeds(embed).queue();
}
}

View File

@@ -6,6 +6,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.ClearChat;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -15,37 +17,53 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ClearCommand implements MessageCommand
@Component
public class MessageClearCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Collections.singletonList(ClearChat.getLabel()));
private final ClearChat clearChat;
public MessageClearCommand(@Autowired ClearChat clearChat)
{
this.clearChat = clearChat;
}
@Override
public List<Permission> getPermissions() { return Collections.singletonList(ClearChat.getPermission()); }
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList(clearChat.getLabel()));
}
@Override
public boolean passRawArgs() {
public List<Permission> getPermissions()
{
return Collections.singletonList(clearChat.getPermission());
}
@Override
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.MODERATION;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Clear the current channel's chat history.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "[amount]";
}
@@ -53,8 +71,9 @@ public class ClearCommand implements MessageCommand
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
// check if user is trying to run command in dms.
String error = ClearChat.checkDMs(event.getChannel());
if (error != null) {
String error = clearChat.checkDMs(event.getChannel());
if (error != null)
{
event.getMessage().reply(error).queue();
return;
}
@@ -64,7 +83,8 @@ public class ClearCommand implements MessageCommand
if (args.length == 0) toDeleteAmount = 1;
else
{
try {
try
{
toDeleteAmount = Integer.parseInt(args[0]);
} catch (NumberFormatException e)
{
@@ -73,10 +93,11 @@ public class ClearCommand implements MessageCommand
}
// cap the amount to avoid abuse.
if(toDeleteAmount > ClearChat.getMaxAmount()) toDeleteAmount = 0;
if (toDeleteAmount > clearChat.getMaxAmount()) toDeleteAmount = 0;
error = ClearChat.checkDeleteAmount(toDeleteAmount);
if (error != null) {
error = clearChat.checkDeleteAmount(toDeleteAmount);
if (error != null)
{
event.getMessage().reply(error).queue();
return;
}
@@ -85,20 +106,20 @@ public class ClearCommand implements MessageCommand
String content = "\uD83D\uDEA7 Clearing...";
Message botMessage = event.getMessage().reply(content).complete();
int deleted = ClearChat.delete(toDeleteAmount,
int deleted = clearChat.delete(toDeleteAmount,
event.getMessageIdLong(),
event.getChannel());
// get a nicely formatted message that logs the deletion of messages.
content = ClearChat.parseAmount(deleted);
content = clearChat.parseAmount(deleted);
// edit the message text and attach a button.
Button dismiss = ClearChat.getDismissButton();
Button dismiss = clearChat.getDismissButton();
Message finalMessage = event.getChannel().sendMessage(content).setActionRow(dismiss).complete();
// add the message to database.
Cache.getDatabaseSource().queueDisabling(finalMessage);
Cache.getDatabaseSource().trackRanCommandReply(finalMessage, event.getAuthor());
Cache.getServices().databaseService().queueDisabling(finalMessage);
Cache.getServices().databaseService().trackRanCommandReply(finalMessage, event.getAuthor());
// delete the sender's message.
event.getMessage().delete().queue();

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.CoinFlip;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -12,53 +14,69 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class CoinFlipCommand implements MessageCommand
@Component
public class MessageCoinFlipCommand implements MessageCommand
{
private final CoinFlip coinFlip;
public MessageCoinFlipCommand(@Autowired CoinFlip coinFlip)
{
this.coinFlip = coinFlip;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("coinflip", "flip", "flipcoin"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // null because it can be used anywhere
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Flip a coin.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) {
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
// perform coin flip
event.getMessage().reply(CoinFlip.genRandom())
.addActionRow(CoinFlip.getReflipButton())
event.getMessage().reply(coinFlip.genRandom())
.addActionRow(coinFlip.getReflipButton())
.queue((message) ->
{
// set the command as expiring and restrict it to the user who ran it
CoinFlip.trackAndRestrict(message, event.getAuthor());
}, (error) -> {});
coinFlip.trackAndRestrict(message, event.getAuthor());
}, (error) -> {
});
}
}

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.DiceRoll;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -13,21 +15,32 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class DiceRollCommand implements MessageCommand
@Component
public class MessageDiceRollCommand implements MessageCommand
{
private final DiceRoll diceRoll;
public MessageDiceRollCommand(@Autowired DiceRoll diceRoll)
{
this.diceRoll = diceRoll;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("diceroll", "droll", "roll"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@@ -47,13 +60,15 @@ public class DiceRollCommand implements MessageCommand
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "[dice size] [rolls]";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@@ -61,7 +76,7 @@ public class DiceRollCommand implements MessageCommand
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
MessageResponse response = DiceRoll.buildResponse(event.getAuthor(), args);
MessageResponse response = diceRoll.buildResponse(event.getAuthor(), args);
if (response.content() != null)
{

View File

@@ -4,6 +4,7 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -11,37 +12,46 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class HelloCommand implements MessageCommand
@Component
public class MessageHelloCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("hi", "hello", "heya"));
}
@Override
public List<Permission> getPermissions() { return null; }
public List<Permission> getPermissions()
{
return null;
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get pinged by the bot.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}

View File

@@ -6,6 +6,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.apache.commons.text.WordUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.Alias;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -13,39 +15,54 @@ import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.*;
public class HelpCommand implements MessageCommand
@Component
public class MessageHelpCommand implements MessageCommand
{
private final Alias alias;
public MessageHelpCommand(@Autowired Alias alias)
{
this.alias = alias;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("help"));
}
@Nullable
@Override
public List<Permission> getPermissions() { return null; }
public List<Permission> getPermissions()
{
return null;
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get general help on the bot. Specify a command if you want specific help about that command.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "[command]";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}
@@ -102,7 +119,8 @@ public class HelpCommand implements MessageCommand
}
event.getMessage().replyEmbeds(embedBuilder.build()).queue();
} else {
} else
{
String commandLabel = args[0].toLowerCase();
MessageCommand command = Cache.getMessageCommandListener().getRegisteredCommand(commandLabel);
@@ -118,14 +136,15 @@ public class HelpCommand implements MessageCommand
if (internalUsage != null) usage += " " + internalUsage;
usage += "`";
String aliases = Alias.generateNiceAliases(command);
String aliases = alias.generateNiceAliases(command);
List<Permission> permissions = command.getPermissions();
StringBuilder permissionsStringBuilder = new StringBuilder();
if (permissions == null)
{
permissionsStringBuilder = new StringBuilder("Available to everyone");
} else {
} else
{
for (int i = 0; i < permissions.size(); i++)
{
Permission permission = permissions.get(i);

View File

@@ -7,6 +7,8 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.Invite;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -15,40 +17,53 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class InviteCommand implements MessageCommand
@Component
public class MessageInviteCommand implements MessageCommand
{
private final Invite invite;
public MessageInviteCommand(@Autowired Invite invite)
{
this.invite = invite;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("invite"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null;
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Get the bot's invite link.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.MODERATION;
}
@@ -57,8 +72,8 @@ public class InviteCommand implements MessageCommand
{
MessageEmbed inviteEmbed = Invite.generateEmbed();
Button inviteButton = Invite.getInviteButton();
MessageEmbed inviteEmbed = invite.generateEmbed();
Button inviteButton = invite.getInviteButton();
// if this is a guild, don't spam the invite in public but DM it
if (event.getChannelType().isGuild())
@@ -70,7 +85,8 @@ public class InviteCommand implements MessageCommand
.queue();
event.getMessage().addReaction(Emoji.fromUnicode("")).queue();
}, error -> event.getMessage().addReaction(Emoji.fromUnicode("")).queue());
} else {
} else
{
event.getMessage()
.replyEmbeds(inviteEmbed)
.addActionRow(inviteButton)

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -13,46 +15,59 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class KickCommand implements MessageCommand
@Component
public class MessageKickCommand implements MessageCommand
{
private final UserPunishment userPunishment;
public MessageKickCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("kick"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return new ArrayList<Permission>(Collections.singletonList(Permission.KICK_MEMBERS));
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.MODERATION;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Kick the mentioned user from the guild.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<mentioned user> [reason]";
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
UserPunishment.handle(event, args, UserPunishment.PunishmentType.KICK);
userPunishment.handle(event, args, UserPunishment.PunishmentType.KICK);
}
}

View File

@@ -8,6 +8,8 @@ import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.commands.base.LoveCalculator;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
@@ -17,8 +19,15 @@ import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class LoveCalculatorCommand implements MessageCommand
@Component
public class MessageLoveCalculatorCommand implements MessageCommand
{
private final LoveCalculator loveCalculator;
public MessageLoveCalculatorCommand(@Autowired LoveCalculator loveCalculator)
{
this.loveCalculator = loveCalculator;
}
@Override
@@ -29,30 +38,35 @@ public class LoveCalculatorCommand implements MessageCommand
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; //anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Calculate how much two people love each other. You can mention two people or just one.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<person 1> [person 2]";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@@ -80,12 +94,13 @@ public class LoveCalculatorCommand implements MessageCommand
if (mentions.size() == 1)
{
user2 = event.getAuthor();
} else {
} else
{
mentionedUserId = mentions.get(1).getId();
user2 = HidekoBot.getAPI().retrieveUserById(mentionedUserId).complete();
}
MessageEmbed embed = LoveCalculator.buildEmbedAndCacheResult(event.getAuthor(), user1, user2);
MessageEmbed embed = loveCalculator.buildEmbedAndCacheResult(event.getAuthor(), user1, user2);
event.getChannel().sendMessageEmbeds(embed).queue();
}

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.MagicBall;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -11,40 +13,53 @@ import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.LinkedList;
import java.util.List;
public class MagicBallCommand implements MessageCommand
@Component
public class MessageMagicBallCommand implements MessageCommand
{
private final MagicBall magicBall;
public MessageMagicBallCommand(@Autowired MagicBall magicBall)
{
this.magicBall = magicBall;
}
@Override
public LinkedList<String> getCommandLabels() {
return MagicBall.getLabels();
public LinkedList<String> getCommandLabels()
{
return magicBall.getLabels();
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Ask a question to the Magic Ball.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<question>";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@@ -69,6 +84,6 @@ public class MagicBallCommand implements MessageCommand
String question = questionBuilder.toString();
event.getChannel().sendMessageEmbeds(MagicBall.generateEmbed(question, event.getAuthor())).queue();
event.getChannel().sendMessageEmbeds(magicBall.generateEmbed(question, event.getAuthor())).queue();
}
}

View File

@@ -5,6 +5,8 @@ import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.Say;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -13,39 +15,54 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class SayCommand implements MessageCommand
@Component
public class MessageSayCommand implements MessageCommand
{
private final Say say;
public MessageSayCommand(@Autowired Say say)
{
this.say = say;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("say"));
}
@Nullable
@Override
public List<Permission> getPermissions() { return Collections.singletonList(Say.getPermission()); }
public List<Permission> getPermissions()
{
return Collections.singletonList(say.getPermission());
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return true;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Make the bot say something for you.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<text>";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.TOOLS;
}

View File

@@ -4,6 +4,8 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -13,46 +15,59 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class TimeoutCommand implements MessageCommand
@Component
public class MessageTimeoutCommand implements MessageCommand
{
private final UserPunishment userPunishment;
public MessageTimeoutCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("timeout"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return new ArrayList<Permission>(Collections.singletonList(Permission.MODERATE_MEMBERS));
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.MODERATION;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Timeout the mentioned user.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<mentioned user> <duration> [reason]";
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
UserPunishment.handle(event, args, UserPunishment.PunishmentType.TIMEOUT);
userPunishment.handle(event, args, UserPunishment.PunishmentType.TIMEOUT);
}
}

View File

@@ -19,40 +19,46 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TriviaCommand implements MessageCommand
public class MessageTriviaCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Collections.singletonList("trivia"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null;
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Start a Trivia session and play with others!";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return null;
}
@@ -83,12 +89,13 @@ public class TriviaCommand implements MessageCommand
if (response.content() != null) responseAction = recvMessage.reply(response.content());
else if (response.embed() != null) responseAction = recvMessage.replyEmbeds(response.embed());
if(responseAction != null) {
if (responseAction != null)
{
if (response.components() != null) responseAction = responseAction.addActionRow(response.components());
responseAction.queue(message -> {
Cache.getDatabaseSource().trackRanCommandReply(message, event.getAuthor());
Cache.getDatabaseSource().queueDisabling(message);
Cache.getServices().databaseService().trackRanCommandReply(message, event.getAuthor());
Cache.getServices().databaseService().queueDisabling(message);
});
}

View File

@@ -17,40 +17,46 @@ import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class UrbanDictionaryCommand implements MessageCommand
public class MessageUrbanDictionaryCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
public LinkedList<String> getCommandLabels()
{
return UrbanDictionary.getCommandLabels();
}
@Nullable
@Override
public List<Permission> getPermissions() {
public List<Permission> getPermissions()
{
return null; //anyone can use it
}
@Override
public boolean passRawArgs() {
public boolean passRawArgs()
{
return false;
}
@NotNull
@Override
public String getDescription() {
public String getDescription()
{
return "Look something up in the Urban Dictionary.";
}
@Nullable
@Override
public String getUsage() {
public String getUsage()
{
return "<query>";
}
@NotNull
@Override
public CommandCategory getCategory() {
public CommandCategory getCategory()
{
return CommandCategory.FUN;
}
@@ -66,7 +72,8 @@ public class UrbanDictionaryCommand implements MessageCommand
// sanitize args by only keeping letters and numbers, and adding "+" instead of spaces for HTML parsing
StringBuilder termBuilder = new StringBuilder();
for (int i = 0; i < args.length; i++) {
for (int i = 0; i < args.length; i++)
{
String arg = args[i];
termBuilder.append(arg);
@@ -79,9 +86,11 @@ public class UrbanDictionaryCommand implements MessageCommand
Document doc;
try {
try
{
doc = Jsoup.connect(url).get();
} catch (IOException e) {
} catch (IOException e)
{
event.getMessage().reply(UrbanDictionary.getTermNotFoundError()).queue();
return;
}

View File

@@ -7,14 +7,24 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.ProfileImage;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class AvatarCommand extends SlashCommandImpl
@Component
public class SlashAvatarCommand extends SlashCommandImpl
{
private final ProfileImage profileImage;
public SlashAvatarCommand(@NotNull ProfileImage profileImage)
{
this.profileImage = profileImage;
}
@Override
public CommandData getSlashCommandData() {
public CommandData getSlashCommandData()
{
return Commands.slash("avatar", "Get someone's profile picture.")
.addOption(OptionType.USER, "user", "User you want to grab the avatar of.")
.addOption(OptionType.INTEGER, "size", "The size of the returned image.",
@@ -35,19 +45,21 @@ public class AvatarCommand extends SlashCommandImpl
if (userArg != null)
{
user = userArg.getAsUser();
} else {
} else
{
user = event.getUser();
}
OptionMapping sizeArg = event.getOption("size");
if (sizeArg != null)
{
resolution = ProfileImage.parseResolution(sizeArg.getAsInt());
} else {
resolution = ProfileImage.parseResolution(512);
resolution = profileImage.parseResolution(sizeArg.getAsInt());
} else
{
resolution = profileImage.parseResolution(512);
}
MessageResponse response = ProfileImage.buildResponse(resolution, user, ProfileImage.ImageType.AVATAR);
MessageResponse response = profileImage.buildResponse(resolution, user, ProfileImage.ImageType.AVATAR);
if (response.content() != null)
{
event.getHook().editOriginal(response.content()).queue();

View File

@@ -7,11 +7,21 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class BanCommand extends SlashCommandImpl
@Component
public class SlashBanCommand extends SlashCommandImpl
{
private final UserPunishment userPunishment;
public SlashBanCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public CommandData getSlashCommandData()
{
@@ -31,6 +41,6 @@ public class BanCommand extends SlashCommandImpl
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
UserPunishment.handle(event, UserPunishment.PunishmentType.BAN);
userPunishment.handle(event, UserPunishment.PunishmentType.BAN);
}
}

View File

@@ -7,14 +7,24 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.ProfileImage;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class BannerCommand extends SlashCommandImpl
@Component
public class SlashBannerCommand extends SlashCommandImpl
{
private final ProfileImage profileImage;
public SlashBannerCommand(@NotNull ProfileImage profileImage)
{
this.profileImage = profileImage;
}
@Override
public CommandData getSlashCommandData() {
public CommandData getSlashCommandData()
{
return Commands.slash("banner", "Get someone's profile banner.")
.addOption(OptionType.USER, "user", "User you want to grab the banner of.")
.addOption(OptionType.INTEGER, "size", "The size of the returned image.",
@@ -35,19 +45,21 @@ public class BannerCommand extends SlashCommandImpl
if (userArg != null)
{
user = userArg.getAsUser();
} else {
} else
{
user = event.getUser();
}
OptionMapping sizeArg = event.getOption("size");
if (sizeArg != null)
{
resolution = ProfileImage.parseResolution(sizeArg.getAsInt());
} else {
resolution = ProfileImage.parseResolution(512);
resolution = profileImage.parseResolution(sizeArg.getAsInt());
} else
{
resolution = profileImage.parseResolution(512);
}
MessageResponse response = ProfileImage.buildResponse(resolution, user, ProfileImage.ImageType.BANNER);
MessageResponse response = profileImage.buildResponse(resolution, user, ProfileImage.ImageType.BANNER);
if (response.content() != null)
{
event.getHook().editOriginal(response.content()).queue();

View File

@@ -5,6 +5,8 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.BotInfo;
import wtf.beatrice.hidekobot.objects.commands.SlashCommand;
@@ -13,10 +15,20 @@ import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
import java.util.LinkedList;
import java.util.List;
public class BotInfoCommand extends SlashCommandImpl
@Component
public class SlashBotInfoCommand extends SlashCommandImpl
{
private final BotInfo botInfo;
public SlashBotInfoCommand(@Autowired BotInfo botInfo)
{
this.botInfo = botInfo;
}
@Override
public CommandData getSlashCommandData() {
public CommandData getSlashCommandData()
{
return Commands.slash("botinfo", "Get info about the bot.");
}
@@ -36,7 +48,7 @@ public class BotInfoCommand extends SlashCommandImpl
}
// send the list
MessageEmbed embed = BotInfo.generateEmbed(registeredCommandNames);
MessageEmbed embed = botInfo.generateEmbed(registeredCommandNames);
event.getHook().editOriginalEmbeds(embed).queue();
}
}

View File

@@ -9,19 +9,29 @@ import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.ClearChat;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class ClearCommand extends SlashCommandImpl
@Component
public class SlashClearCommand extends SlashCommandImpl
{
private final ClearChat clearChat;
public SlashClearCommand(@Autowired ClearChat clearChat)
{
this.clearChat = clearChat;
}
@Override
public CommandData getSlashCommandData() {
return Commands.slash(ClearChat.getLabel(),
ClearChat.getDescription())
public CommandData getSlashCommandData()
{
return Commands.slash(clearChat.getLabel(),
clearChat.getDescription())
.addOption(OptionType.INTEGER, "amount", "The amount of messages to delete.")
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(ClearChat.getPermission()));
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(clearChat.getPermission()));
}
@Override
@@ -31,7 +41,7 @@ public class ClearCommand extends SlashCommandImpl
event.deferReply().queue();
// check if user is trying to run command in dms.
String error = ClearChat.checkDMs(event.getChannel());
String error = clearChat.checkDMs(event.getChannel());
if (error != null)
{
event.getHook().editOriginal(error).queue();
@@ -45,9 +55,9 @@ public class ClearCommand extends SlashCommandImpl
int toDeleteAmount = amountOption == null ? 1 : amountOption.getAsInt();
// cap the amount to avoid abuse.
if(toDeleteAmount > ClearChat.getMaxAmount()) toDeleteAmount = 0;
if (toDeleteAmount > clearChat.getMaxAmount()) toDeleteAmount = 0;
error = ClearChat.checkDeleteAmount(toDeleteAmount);
error = clearChat.checkDeleteAmount(toDeleteAmount);
if (error != null)
{
event.getHook().editOriginal(error).queue();
@@ -59,20 +69,20 @@ public class ClearCommand extends SlashCommandImpl
Message botMessage = event.getHook().editOriginal(content).complete();
// actually delete the messages.
int deleted = ClearChat.delete(toDeleteAmount,
int deleted = clearChat.delete(toDeleteAmount,
event.getInteraction().getIdLong(),
event.getChannel());
// get a nicely formatted message that logs the deletion of messages.
content = ClearChat.parseAmount(deleted);
content = clearChat.parseAmount(deleted);
// edit the message text and attach a button.
Button dismiss = ClearChat.getDismissButton();
Button dismiss = clearChat.getDismissButton();
botMessage = botMessage.editMessage(content).setActionRow(dismiss).complete();
// add the message to database.
Cache.getDatabaseSource().queueDisabling(botMessage);
Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getUser());
Cache.getServices().databaseService().queueDisabling(botMessage);
Cache.getServices().databaseService().trackRanCommandReply(botMessage, event.getUser());
}
}

View File

@@ -4,11 +4,20 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.CoinFlip;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class CoinFlipCommand extends SlashCommandImpl
@Component
public class SlashCoinFlipCommand extends SlashCommandImpl
{
private final CoinFlip coinFlip;
public SlashCoinFlipCommand(@Autowired CoinFlip coinFlip)
{
this.coinFlip = coinFlip;
}
@Override
public CommandData getSlashCommandData()
@@ -21,20 +30,19 @@ public class CoinFlipCommand extends SlashCommandImpl
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
// perform coin flip
event.reply(CoinFlip.genRandom())
.addActionRow(CoinFlip.getReflipButton())
event.reply(coinFlip.genRandom())
.addActionRow(coinFlip.getReflipButton())
.queue((interaction) ->
{
// set the command as expiring and restrict it to the user who ran it
interaction.retrieveOriginal().queue((message) ->
{
CoinFlip.trackAndRestrict(message, event.getUser());
}, (error) -> {});
}, (error) -> {});
coinFlip.trackAndRestrict(message, event.getUser());
}, (error) -> {
});
}, (error) -> {
});
}
}

View File

@@ -6,12 +6,21 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.DiceRoll;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class DiceRollCommand extends SlashCommandImpl
@Component
public class SlashDiceRollCommand extends SlashCommandImpl
{
private final DiceRoll diceRoll;
public SlashDiceRollCommand(@NotNull DiceRoll diceRoll)
{
this.diceRoll = diceRoll;
}
@Override
public CommandData getSlashCommandData()
{
@@ -37,7 +46,7 @@ public class DiceRollCommand extends SlashCommandImpl
String[] args = messageContent.split("\\s");
MessageResponse response = DiceRoll.buildResponse(event.getUser(), args);
MessageResponse response = diceRoll.buildResponse(event.getUser(), args);
if (response.content() != null)
{

View File

@@ -13,10 +13,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DieCommand extends SlashCommandImpl
public class SlashDieCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData() {
public CommandData getSlashCommandData()
{
return Commands.slash("die", "Stop the bot's process.")
.setDefaultPermissions(DefaultMemberPermissions.DISABLED);
}
@@ -27,9 +28,11 @@ public class DieCommand extends SlashCommandImpl
if (Cache.getBotOwnerId() != event.getUser().getIdLong())
{
event.reply("Sorry, only the bot owner can run this command!").setEphemeral(true).queue();
} else {
} else
{
event.reply("Going to sleep! Cya ✨").queue();
try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor()) {
try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor())
{
executor.schedule(HidekoBot::shutdown, 3, TimeUnit.SECONDS);
}
}

View File

@@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class HelpCommand extends SlashCommandImpl
public class SlashHelpCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()

View File

@@ -10,11 +10,19 @@ import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction;
import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.Invite;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class InviteCommand extends SlashCommandImpl
@Component
public class SlashInviteCommand extends SlashCommandImpl
{
private final Invite invite;
public SlashInviteCommand(@NotNull Invite invite)
{
this.invite = invite;
}
@Override
public CommandData getSlashCommandData()
@@ -36,8 +44,8 @@ public class InviteCommand extends SlashCommandImpl
}
replyCallbackAction.queue();
MessageEmbed inviteEmbed = Invite.generateEmbed();
Button inviteButton = Invite.getInviteButton();
MessageEmbed inviteEmbed = invite.generateEmbed();
Button inviteButton = invite.getInviteButton();
WebhookMessageEditAction<Message> reply =
event.getHook()

View File

@@ -7,11 +7,22 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class KickCommand extends SlashCommandImpl
@Component
public class SlashKickCommand extends SlashCommandImpl
{
private final UserPunishment userPunishment;
public SlashKickCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public CommandData getSlashCommandData()
{
@@ -31,6 +42,6 @@ public class KickCommand extends SlashCommandImpl
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
UserPunishment.handle(event, UserPunishment.PunishmentType.KICK);
userPunishment.handle(event, UserPunishment.PunishmentType.KICK);
}
}

View File

@@ -8,11 +8,20 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.LoveCalculator;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class LoveCalculatorCommand extends SlashCommandImpl
@Component
public class SlashLoveCalculatorCommand extends SlashCommandImpl
{
private final LoveCalculator loveCalculator;
public SlashLoveCalculatorCommand(@NotNull LoveCalculator loveCalculator)
{
this.loveCalculator = loveCalculator;
}
@Override
public CommandData getSlashCommandData()
{
@@ -40,7 +49,8 @@ public class LoveCalculatorCommand extends SlashCommandImpl
if (firsUserArg != null)
{
firstUser = firsUserArg.getAsUser(); //todo null check?
} else {
} else
{
event.reply("\uD83D\uDE22 I need to know who to check! Please mention them.")
.setEphemeral(true)
.queue();
@@ -51,11 +61,12 @@ public class LoveCalculatorCommand extends SlashCommandImpl
if (secondUserArg != null)
{
secondUser = secondUserArg.getAsUser(); //todo null check?
} else {
} else
{
secondUser = event.getUser();
}
MessageEmbed embed = LoveCalculator.buildEmbedAndCacheResult(event.getUser(), firstUser, secondUser);
MessageEmbed embed = loveCalculator.buildEmbedAndCacheResult(event.getUser(), firstUser, secondUser);
event.replyEmbeds(embed).queue();
}
}

View File

@@ -7,16 +7,25 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.MagicBall;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class MagicBallCommand extends SlashCommandImpl
@Component
public class SlashMagicBallCommand extends SlashCommandImpl
{
private final MagicBall magicBall;
public SlashMagicBallCommand(@NotNull MagicBall magicBall)
{
this.magicBall = magicBall;
}
@Override
public CommandData getSlashCommandData()
{
return Commands.slash(MagicBall.getLabels().get(0),
return Commands.slash(magicBall.getLabels().get(0),
"Ask a question to the magic ball.")
.addOption(OptionType.STRING, "question",
"The question to ask.",
@@ -43,7 +52,7 @@ public class MagicBallCommand extends SlashCommandImpl
return;
}
MessageEmbed response = MagicBall.generateEmbed(question, event.getUser());
MessageEmbed response = magicBall.generateEmbed(question, event.getUser());
event.replyEmbeds(response).queue();
}
}

View File

@@ -6,7 +6,7 @@ import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class PingCommand extends SlashCommandImpl
public class SlashPingCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()

View File

@@ -8,11 +8,20 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.Say;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class SayCommand extends SlashCommandImpl
@Component
public class SlashSayCommand extends SlashCommandImpl
{
private final Say say;
public SlashSayCommand(@NotNull Say say)
{
this.say = say;
}
@Override
public CommandData getSlashCommandData()
{
@@ -22,7 +31,7 @@ public class SayCommand extends SlashCommandImpl
"The message to send.",
true,
false)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Say.getPermission()));
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(say.getPermission()));
}
@Override

View File

@@ -7,11 +7,22 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.UserPunishment;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class TimeoutCommand extends SlashCommandImpl
@Component
public class SlashTimeoutCommand extends SlashCommandImpl
{
private final UserPunishment userPunishment;
public SlashTimeoutCommand(@Autowired UserPunishment userPunishment)
{
this.userPunishment = userPunishment;
}
@Override
public CommandData getSlashCommandData()
{
@@ -35,6 +46,6 @@ public class TimeoutCommand extends SlashCommandImpl
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
UserPunishment.handle(event, UserPunishment.PunishmentType.TIMEOUT);
userPunishment.handle(event, UserPunishment.PunishmentType.TIMEOUT);
}
}

View File

@@ -11,7 +11,7 @@ import wtf.beatrice.hidekobot.commands.base.Trivia;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class TriviaCommand extends SlashCommandImpl
public class SlashTriviaCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()
@@ -44,8 +44,8 @@ public class TriviaCommand extends SlashCommandImpl
event.getHook().editOriginalEmbeds(response.embed()).setActionRow(response.components()).queue(message ->
{
Cache.getDatabaseSource().trackRanCommandReply(message, event.getUser());
Cache.getDatabaseSource().queueDisabling(message);
Cache.getServices().databaseService().trackRanCommandReply(message, event.getUser());
Cache.getServices().databaseService().queueDisabling(message);
});
}
}

View File

@@ -17,7 +17,7 @@ import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
import java.io.IOException;
public class UrbanDictionaryCommand extends SlashCommandImpl
public class SlashUrbanDictionaryCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()
@@ -54,9 +54,11 @@ public class UrbanDictionaryCommand extends SlashCommandImpl
Document doc;
try {
try
{
doc = Jsoup.connect(url).get();
} catch (IOException e) {
} catch (IOException e)
{
event.reply(UrbanDictionary.getTermNotFoundError())
.setEphemeral(true)
.queue();

View File

@@ -14,12 +14,20 @@ public enum ConfigurationEntry
private String path;
private Object defaultValue;
ConfigurationEntry(String path, Object defaultValue)
{
this.path = path;
this.defaultValue = defaultValue;
}
public String getPath() { return path; }
public Object getDefaultValue() { return defaultValue; }
public String getPath()
{
return path;
}
public Object getDefaultValue()
{
return defaultValue;
}
}

View File

@@ -49,15 +49,16 @@ public class ConfigurationSource
if (!fsConfigFile.exists())
{
// try to create config file
try {
try
{
if (!fsConfigFile.createNewFile())
{
LOGGER.error("We tried creating a file that already exists!");
HidekoBot.shutdown();
return;
}
}
catch (IOException e) {
} catch (IOException e)
{
LOGGER.error("Error creating configuration file!", e);
HidekoBot.shutdown();
return;
@@ -68,8 +69,12 @@ public class ConfigurationSource
Yaml fsConfigYaml = new Yaml(new SafeConstructor(options));
LinkedHashMap<String, Object> fsConfigContents = null; // map holding all file entries
try (InputStream fsConfigStream = new FileInputStream(fsConfigFile))
{ fsConfigContents = fsConfigYaml.load(fsConfigStream); }
catch (IOException e) { LOGGER.error(e.getMessage()); }
{
fsConfigContents = fsConfigYaml.load(fsConfigStream);
} catch (IOException e)
{
LOGGER.error(e.getMessage());
}
if (fsConfigContents == null) // if file contents are empty or corrupted...
@@ -106,13 +111,15 @@ public class ConfigurationSource
{
// if the key already exists, copy the original value
filledEntries.put(key, fsConfigContents.get(key));
} else {
} else
{
// else, copy the value from the example config file
filledEntries.put(key, entry.getValue());
}
}
try {
try
{
// new writer to actually write the contents to the file
PrintWriter missingKeysWriter = new PrintWriter(fsConfigFile);
@@ -125,7 +132,8 @@ public class ConfigurationSource
// create the yaml object and dump the values to filesystem
Yaml yaml = new Yaml(dumperOptions);
yaml.dump(filledEntries, missingKeysWriter);
} catch (FileNotFoundException e) {
} catch (FileNotFoundException e)
{
LOGGER.error(e.getMessage());
HidekoBot.shutdown();
return;
@@ -133,7 +141,8 @@ public class ConfigurationSource
// finally, dump all entries to cache.
loadConfig(filledEntries);
} else {
} else
{
// if no key is missing, just cache all entries and values from filesystem.
loadConfig(fsConfigContents);
}
@@ -144,6 +153,7 @@ public class ConfigurationSource
{
this.configurationEntries.putAll(configurationEntries);
}
public Object getConfigValue(ConfigurationEntry key)
{
return configurationEntries.get(key.getPath());

View File

@@ -1,592 +0,0 @@
package wtf.beatrice.hidekobot.datasources;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import org.slf4j.LoggerFactory;
import wtf.beatrice.hidekobot.Cache;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class DatabaseSource {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DatabaseSource.class);
private static final String JDBC_URL = "jdbc:sqlite:%path%";
private Connection dbConnection = null;
private final String dbPath;
public DatabaseSource(String dbPath) {
this.dbPath = dbPath;
}
private void logException(SQLException e) {
LOGGER.error("Database Exception", e);
}
public boolean connect() {
String url = JDBC_URL.replace("%path%", dbPath);
if (!close()) return false;
try {
dbConnection = DriverManager.getConnection(url);
LOGGER.info("Database connection established!");
return true;
} catch (SQLException e) {
logException(e);
return false;
}
}
public boolean close() {
if (dbConnection != null) {
try {
if (!dbConnection.isClosed()) {
dbConnection.close();
}
} catch (SQLException e) {
logException(e);
return false;
}
dbConnection = null;
}
return true;
}
/*
* DB STRUCTURE
* TABLE 1: pending_disabled_messages
* ----------------------------------------------------------------------------------
* | guild_id | channel_id | message_id | expiry_timestamp |
* ----------------------------------------------------------------------------------
* |39402849302 | 39402849302 | 39402849302 | 2022-11-20 22:45:53:300 |
* ---------------------------------------------------------------------------------
*
*
* TABLE 2: command_runners
* --------------------------------------------------------------------------------------------
* | guild_id | channel_id | message_id | user_id | channel_type |
* --------------------------------------------------------------------------------------------
* | 39402849302 | 39402849302 | 39402849302 | 39402849302 | PRIVATE |
* --------------------------------------------------------------------------------------------
*
* TABLE 3: urban_dictionary
* -----------------------------------------------------------------------------------------------------
* | message_id | page | meanings | examples | contributors | dates | term |
* -----------------------------------------------------------------------------------------------------
* | 39402849302 | 0 | base64 | base64 | base64 | base64 | miku |
* -----------------------------------------------------------------------------------------------------
*/
//todo: javadocs
public boolean initDb() {
List<String> newTables = new ArrayList<>();
newTables.add("""
CREATE TABLE IF NOT EXISTS pending_disabled_messages (
guild_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
message_id TEXT NOT NULL,
expiry_timestamp TEXT NOT NULL);
""");
newTables.add("""
CREATE TABLE IF NOT EXISTS command_runners (
guild_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
message_id TEXT NOT NULL,
user_id TEXT NOT NULL,
channel_type TEXT NOT NULL);
""");
newTables.add("""
CREATE TABLE IF NOT EXISTS urban_dictionary (
message_id TEXT NOT NULL,
page INTEGER NOT NULL,
meanings TEXT NOT NULL,
examples TEXT NOT NULL,
contributors TEXT NOT NULL,
dates TEXT NOT NULL,
term TEXT NOT NULL
);
""");
for (String sql : newTables) {
try (Statement stmt = dbConnection.createStatement()) {
// execute the statement
stmt.execute(sql);
} catch (SQLException e) {
logException(e);
return false;
}
}
return true;
}
public boolean trackRanCommandReply(Message message, User user) {
String userId = user.getId();
String guildId;
ChannelType channelType = message.getChannelType();
if (!(channelType.isGuild())) {
guildId = userId;
} else {
guildId = message.getGuild().getId();
}
String channelId = message.getChannel().getId();
String messageId = message.getId();
String query = """
INSERT INTO command_runners
(guild_id, channel_id, message_id, user_id, channel_type) VALUES
(?, ?, ?, ?, ?);
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, guildId);
preparedStatement.setString(2, channelId);
preparedStatement.setString(3, messageId);
preparedStatement.setString(4, userId);
preparedStatement.setString(5, channelType.name());
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
logException(e);
}
return false;
}
public boolean isUserTrackedFor(String userId, String messageId) {
String trackedUserId = getTrackedReplyUserId(messageId);
if (trackedUserId == null) return false;
return userId.equals(trackedUserId);
}
public ChannelType getTrackedMessageChannelType(String messageId) {
String query = """
SELECT channel_type
FROM command_runners
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
String channelTypeName = resultSet.getString("channel_type");
return ChannelType.valueOf(channelTypeName);
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getTrackedReplyUserId(String messageId) {
String query = """
SELECT user_id
FROM command_runners
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("user_id");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public boolean queueDisabling(Message message) {
String messageId = message.getId();
String channelId = message.getChannel().getId();
String guildId;
ChannelType channelType = message.getChannelType();
if (!(channelType.isGuild())) {
guildId = "PRIVATE";
} else {
guildId = message.getGuild().getId();
}
LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat());
String expiryTimeFormatted = dateTimeFormatter.format(expiryTime);
String query = """
INSERT INTO pending_disabled_messages
(guild_id, channel_id, message_id, expiry_timestamp) VALUES
(?, ?, ?, ?);
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, guildId);
preparedStatement.setString(2, channelId);
preparedStatement.setString(3, messageId);
preparedStatement.setString(4, expiryTimeFormatted);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
logException(e);
}
return false;
}
public List<String> getQueuedExpiringMessages() {
List<String> messages = new ArrayList<>();
String query = """
SELECT message_id
FROM pending_disabled_messages;
""";
try (Statement statement = dbConnection.createStatement()) {
ResultSet resultSet = statement.executeQuery(query);
if (resultSet.isClosed()) return messages;
while (resultSet.next()) {
messages.add(resultSet.getString("message_id"));
}
} catch (SQLException e) {
logException(e);
}
return messages;
}
public boolean untrackExpiredMessage(String messageId) {
String query = "DELETE FROM pending_disabled_messages WHERE message_id = ?;";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
preparedStatement.execute();
} catch (SQLException e) {
logException(e);
return false;
}
query = "DELETE FROM command_runners WHERE message_id = ?;";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
preparedStatement.execute();
} catch (SQLException e) {
logException(e);
return false;
}
query = "DELETE FROM urban_dictionary WHERE message_id = ?;";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
preparedStatement.execute();
} catch (SQLException e) {
logException(e);
return false;
}
return true;
}
public String getQueuedExpiringMessageExpiryDate(String messageId) {
String query = """
SELECT expiry_timestamp
FROM pending_disabled_messages
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("expiry_timestamp");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getQueuedExpiringMessageChannel(String messageId) {
String query = """
SELECT channel_id
FROM pending_disabled_messages
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("channel_id");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getQueuedExpiringMessageGuild(String messageId) {
String query = """
SELECT guild_id
FROM pending_disabled_messages
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("guild_id");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public boolean trackUrban(String meanings, String examples,
String contributors, String dates,
Message message, String term) {
String query = """
INSERT INTO urban_dictionary
(message_id, page, meanings, examples, contributors, dates, term) VALUES
(?, ?, ?, ?, ?, ?, ?);
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, message.getId());
preparedStatement.setInt(2, 0);
preparedStatement.setString(3, meanings);
preparedStatement.setString(4, examples);
preparedStatement.setString(5, contributors);
preparedStatement.setString(6, dates);
preparedStatement.setString(7, term);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
logException(e);
}
return false;
}
public int getUrbanPage(String messageId) {
String query = """
SELECT page
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return 0;
while (resultSet.next()) {
return resultSet.getInt("page");
}
} catch (SQLException e) {
logException(e);
}
return 0;
}
public String getUrbanMeanings(String messageId) {
String query = """
SELECT meanings
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("meanings");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getUrbanExamples(String messageId) {
String query = """
SELECT examples
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("examples");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getUrbanContributors(String messageId) {
String query = """
SELECT contributors
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("contributors");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getUrbanDates(String messageId) {
String query = """
SELECT dates
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("dates");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public String getUrbanTerm(String messageId) {
String query = """
SELECT term
FROM urban_dictionary
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.isClosed()) return null;
while (resultSet.next()) {
return resultSet.getString("term");
}
} catch (SQLException e) {
logException(e);
}
return null;
}
public boolean setUrbanPage(String messageId, int page) {
String query = """
UPDATE urban_dictionary
SET page = ?
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setInt(1, page);
preparedStatement.setString(2, messageId);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
logException(e);
}
return false;
}
public boolean resetExpiryTimestamp(String messageId) {
LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat());
String expiryTimeFormatted = dateTimeFormatter.format(expiryTime);
String query = """
UPDATE pending_disabled_messages
SET expiry_timestamp = ?
WHERE message_id = ?;
""";
try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) {
preparedStatement.setString(1, expiryTimeFormatted);
preparedStatement.setString(2, messageId);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
logException(e);
}
return false;
}
}

View File

@@ -25,8 +25,8 @@ public class PropertiesSource
{
properties.load(internalPropertiesStream);
}
catch (IOException e) {
} catch (IOException e)
{
LOGGER.error(e.getMessage());
HidekoBot.shutdown();
return;
@@ -34,5 +34,7 @@ public class PropertiesSource
}
public String getProperty(String property)
{ return properties == null ? "" : properties.getProperty(property); }
{
return properties == null ? "" : properties.getProperty(property);
}
}

View File

@@ -0,0 +1,77 @@
package wtf.beatrice.hidekobot.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "command_runners")
public class CommandRunner
{
@Id
@Column(name = "message_id", nullable = false)
private String messageId;
@Column(name = "guild_id", nullable = false)
private String guildId;
@Column(name = "channel_id", nullable = false)
private String channelId;
@Column(name = "user_id", nullable = false)
private String userId;
@Column(name = "channel_type", nullable = false)
private String channelType; // store JDA enum name
public String getMessageId()
{
return messageId;
}
public void setMessageId(String messageId)
{
this.messageId = messageId;
}
public String getGuildId()
{
return guildId;
}
public void setGuildId(String guildId)
{
this.guildId = guildId;
}
public String getChannelId()
{
return channelId;
}
public void setChannelId(String channelId)
{
this.channelId = channelId;
}
public String getUserId()
{
return userId;
}
public void setUserId(String userId)
{
this.userId = userId;
}
public String getChannelType()
{
return channelType;
}
public void setChannelType(String channelType)
{
this.channelType = channelType;
}
}

View File

@@ -0,0 +1,64 @@
package wtf.beatrice.hidekobot.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "pending_disabled_messages")
public class PendingDisabledMessage
{
@Id
@Column(name = "message_id", nullable = false)
private String messageId;
@Column(name = "guild_id", nullable = false)
private String guildId;
@Column(name = "channel_id", nullable = false)
private String channelId;
@Column(name = "expiry_timestamp", nullable = false)
private String expiryTimestamp; // keep as String to match your format for now
public String getMessageId()
{
return messageId;
}
public void setMessageId(String messageId)
{
this.messageId = messageId;
}
public String getGuildId()
{
return guildId;
}
public void setGuildId(String guildId)
{
this.guildId = guildId;
}
public String getChannelId()
{
return channelId;
}
public void setChannelId(String channelId)
{
this.channelId = channelId;
}
public String getExpiryTimestamp()
{
return expiryTimestamp;
}
public void setExpiryTimestamp(String expiryTimestamp)
{
this.expiryTimestamp = expiryTimestamp;
}
}

View File

@@ -0,0 +1,103 @@
package wtf.beatrice.hidekobot.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "urban_dictionary")
public class UrbanDictionaryEntry
{
@Id
@Column(name = "message_id", nullable = false)
private String messageId;
@Column(name = "page", nullable = false)
private Integer page;
@Column(name = "meanings", nullable = false, columnDefinition = "TEXT")
private String meanings;
@Column(name = "examples", nullable = false, columnDefinition = "TEXT")
private String examples;
@Column(name = "contributors", nullable = false, columnDefinition = "TEXT")
private String contributors;
@Column(name = "dates", nullable = false, columnDefinition = "TEXT")
private String dates;
@Column(name = "term", nullable = false)
private String term;
public String getMessageId()
{
return messageId;
}
public void setMessageId(String messageId)
{
this.messageId = messageId;
}
public Integer getPage()
{
return page;
}
public void setPage(Integer page)
{
this.page = page;
}
public String getMeanings()
{
return meanings;
}
public void setMeanings(String meanings)
{
this.meanings = meanings;
}
public String getExamples()
{
return examples;
}
public void setExamples(String examples)
{
this.examples = examples;
}
public String getContributors()
{
return contributors;
}
public void setContributors(String contributors)
{
this.contributors = contributors;
}
public String getDates()
{
return dates;
}
public void setDates(String dates)
{
this.dates = dates;
}
public String getTerm()
{
return term;
}
public void setTerm(String term)
{
this.term = term;
}
}

View File

@@ -4,27 +4,40 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.CoinFlip;
import wtf.beatrice.hidekobot.commands.base.Trivia;
import wtf.beatrice.hidekobot.commands.base.UrbanDictionary;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.services.CommandService;
@Component
public class ButtonInteractionListener extends ListenerAdapter
{
private static final Logger LOGGER = LoggerFactory.getLogger(ButtonInteractionListener.class);
private final CommandService commandService;
private final CoinFlip coinFlip;
public ButtonInteractionListener(@Autowired CommandService commandService,
@Autowired CoinFlip coinFlip)
{
this.commandService = commandService;
this.coinFlip = coinFlip;
}
@Override
public void onButtonInteraction(ButtonInteractionEvent event)
{
switch (event.getComponentId().toLowerCase()) {
switch (event.getComponentId().toLowerCase())
{
// coinflip
case "coinflip_reflip" -> CoinFlip.buttonReFlip(event);
case "coinflip_reflip" -> coinFlip.buttonReFlip(event);
// generic dismiss button
case "generic_dismiss" -> CommandUtil.delete(event);
case "generic_dismiss" -> commandService.deleteUserLinkedMessage(event);
// urban dictionary navigation
case "urban_nextpage" -> UrbanDictionary.changePage(event, UrbanDictionary.ChangeType.NEXT);

View File

@@ -7,6 +7,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
@@ -14,6 +15,7 @@ import wtf.beatrice.hidekobot.objects.comparators.MessageCommandAliasesComparato
import java.util.*;
@Component
public class MessageCommandListener extends ListenerAdapter
{
@@ -45,7 +47,9 @@ public class MessageCommandListener extends ListenerAdapter
for (String currentAlias : aliases)
{
if (label.equals(currentAlias))
{ return entry.getValue(); }
{
return entry.getValue();
}
}
}
@@ -53,7 +57,9 @@ public class MessageCommandListener extends ListenerAdapter
}
public LinkedList<MessageCommand> getRegisteredCommands()
{ return new LinkedList<>(registeredCommands.values()); }
{
return new LinkedList<>(registeredCommands.values());
}
@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event)
@@ -133,8 +139,7 @@ public class MessageCommandListener extends ListenerAdapter
argsString = argsString.replaceAll("^[\\S]+\\s*", "");
// pass all other arguments as a single argument as the first array element
commandArgs = new String[]{argsString};
}
else
} else
{
// copy all split arguments to the array, except from the command label
commandArgs = Arrays.copyOfRange(argsRaw, 1, argsRaw.length);

View File

@@ -8,7 +8,9 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class MessageLogger extends ListenerAdapter
{
// this class only gets loaded as a listener if verbosity is set to true on startup.
@@ -22,7 +24,7 @@ public class MessageLogger extends ListenerAdapter
public void onMessageReceived(@NotNull MessageReceivedEvent event)
{
String toLog = "";
String userName = event.getAuthor().getAsTag();
String userName = event.getAuthor().getName();
String message = event.getMessage().getContentDisplay();
if (event.getChannel() instanceof TextChannel channel)
@@ -33,8 +35,7 @@ public class MessageLogger extends ListenerAdapter
toLog = GUILD_MESSAGE_LOG_FORMAT
.replace("%guild%", guildName)
.replace("%channel%", channelName);
}
else if(event.getChannel() instanceof PrivateChannel)
} else if (event.getChannel() instanceof PrivateChannel)
{
toLog = DIRECT_MESSAGE_LOG_FORMAT;
}

View File

@@ -4,8 +4,10 @@ import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionE
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.commands.base.Trivia;
@Component
public class SelectMenuInteractionListener extends ListenerAdapter
{
@@ -14,7 +16,8 @@ public class SelectMenuInteractionListener extends ListenerAdapter
@Override
public void onStringSelectInteraction(StringSelectInteractionEvent event)
{
switch (event.getComponentId().toLowerCase()) {
switch (event.getComponentId().toLowerCase())
{
// trivia
case "trivia_categories" -> Trivia.handleMenuSelection(event);

View File

@@ -2,11 +2,13 @@ package wtf.beatrice.hidekobot.listeners;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.objects.commands.SlashArgumentsCompleter;
import java.util.LinkedList;
import java.util.TreeMap;
@Component
public class SlashCommandCompletionListener extends ListenerAdapter
{
@@ -21,10 +23,15 @@ public class SlashCommandCompletionListener extends ListenerAdapter
}
public SlashArgumentsCompleter getRegisteredCompleter(String label)
{ return registeredCompleters.get(label); }
{
return registeredCompleters.get(label);
}
public LinkedList<SlashArgumentsCompleter> getRegisteredCompleters()
{ return new LinkedList<>(registeredCompleters.values()); }
{
return new LinkedList<>(registeredCompleters.values());
}
@Override
public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent event)
{

View File

@@ -3,11 +3,13 @@ package wtf.beatrice.hidekobot.listeners;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.objects.commands.SlashCommand;
import java.util.LinkedList;
import java.util.TreeMap;
@Component
public class SlashCommandListener extends ListenerAdapter
{
@@ -21,10 +23,14 @@ public class SlashCommandListener extends ListenerAdapter
}
public SlashCommand getRegisteredCommand(String label)
{ return registeredCommands.get(label); }
{
return registeredCommands.get(label);
}
public LinkedList<SlashCommand> getRegisteredCommands()
{ return new LinkedList<>(registeredCommands.values()); }
{
return new LinkedList<>(registeredCommands.values());
}
@Override
public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event)

View File

@@ -9,10 +9,12 @@ import java.util.Objects;
public record MessageResponse(@Nullable String content,
@Nullable MessageEmbed embed,
@Nullable ItemComponent... components) {
@Nullable ItemComponent... components)
{
@Override
public boolean equals(Object o) {
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MessageResponse response = (MessageResponse) o;
@@ -22,14 +24,16 @@ public record MessageResponse(@Nullable String content,
}
@Override
public int hashCode() {
public int hashCode()
{
int result = Objects.hash(content, embed);
result = 31 * result + Arrays.hashCode(components);
return result;
}
@Override
public String toString() {
public String toString()
{
return "MessageResponse{" +
"content=" + content +
", embed=" + embed +

View File

@@ -9,10 +9,14 @@ public enum CommandCategory
;
private String emoji;
CommandCategory(String emoji)
{
this.emoji = emoji;
}
public String getEmoji() { return emoji; }
public String getEmoji()
{
return emoji;
}
}

View File

@@ -70,9 +70,7 @@ public interface MessageCommand
*
* @param event the received message event. It should not be used for parsing message contents data as
* the arguments already account for it in a better way.
*
* @param label the command label that was used, taken from all available command aliases.
*
* @param args a pre-formatted list of arguments, excluding the bot prefix and the command name.
* This is useful because command logic won't have to change in case the bot prefix is changed,
* removed, or we switch to another method of triggering commands (ping, trigger words, ...).

View File

@@ -12,6 +12,7 @@ public interface SlashArgumentsCompleter
* @return the command object.
*/
SlashCommand getCommand();
/**
* Run the argument-completion logic by parsing the event and replying accordingly.
*

View File

@@ -6,13 +6,16 @@ import org.jetbrains.annotations.NotNull;
public class SlashArgumentsCompleterImpl implements SlashArgumentsCompleter
{
private final SlashCommand parentCommand;
public SlashArgumentsCompleterImpl(SlashCommand parentCommand)
{
this.parentCommand = parentCommand;
}
public SlashCommand getCommand()
{ return parentCommand; }
{
return parentCommand;
}
public void runCompletion(@NotNull CommandAutoCompleteInteractionEvent event)
{

View File

@@ -21,6 +21,7 @@ public interface SlashCommand
* @return the command data object.
*/
CommandData getSlashCommandData();
/**
* Run the command logic by parsing the event and replying accordingly.
*

View File

@@ -8,17 +8,20 @@ public class SlashCommandImpl implements SlashCommand
{
@Override
public String getCommandName() {
public String getCommandName()
{
return getSlashCommandData().getName();
}
@Override
public CommandData getSlashCommandData() {
public CommandData getSlashCommandData()
{
return null;
}
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event) {
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
event.reply("Base command implementation").queue();
}
}

View File

@@ -6,10 +6,12 @@ import java.util.LinkedList;
/**
* This class gets two linked lists, and compares their first value alphabetically.
*/
public class MessageCommandAliasesComparator implements Comparator<LinkedList<String>> {
public class MessageCommandAliasesComparator implements Comparator<LinkedList<String>>
{
@Override
public int compare(LinkedList<String> linkedList, LinkedList<String> t1) {
public int compare(LinkedList<String> linkedList, LinkedList<String> t1)
{
if (linkedList.isEmpty()) return 0;
if (t1.isEmpty()) return 0;

View File

@@ -7,10 +7,12 @@ import java.util.Comparator;
/**
* This class gets two trivia categories, and compares them by their name.
*/
public class TriviaCategoryComparator implements Comparator<TriviaCategory> {
public class TriviaCategoryComparator implements Comparator<TriviaCategory>
{
@Override
public int compare(TriviaCategory o1, TriviaCategory o2) {
public int compare(TriviaCategory o1, TriviaCategory o2)
{
return CharSequence.compare(o1.categoryName(), o2.categoryName());
}
}

View File

@@ -7,10 +7,12 @@ import java.util.Comparator;
/**
* This class gets two trivia scores, and compares their score.
*/
public class TriviaScoreComparator implements Comparator<TriviaScore> {
public class TriviaScoreComparator implements Comparator<TriviaScore>
{
@Override
public int compare(TriviaScore o1, TriviaScore o2) {
public int compare(TriviaScore o1, TriviaScore o2)
{
return Integer.compare(o2.getScore(), o1.getScore()); // inverted, because higher number should come first
}
}

View File

@@ -1,5 +1,6 @@
package wtf.beatrice.hidekobot.objects.fun;
public record TriviaCategory(String categoryName, int categoryId) {
public record TriviaCategory(String categoryName, int categoryId)
{
}

View File

@@ -3,6 +3,7 @@ package wtf.beatrice.hidekobot.objects.fun;
import java.util.List;
public record TriviaQuestion(String question, String correctAnswer,
List<String> wrongAnswers) {
List<String> wrongAnswers)
{
}

View File

@@ -18,14 +18,20 @@ public class TriviaScore
score += add;
}
public int getScore() { return score; }
public int getScore()
{
return score;
}
public User getUser() { return user; }
public User getUser()
{
return user;
}
@Override
public String toString()
{
return "[" + user.getAsTag() + "," + score + "]";
return "[" + user.getName() + "," + score + "]";
}
}

View File

@@ -0,0 +1,8 @@
package wtf.beatrice.hidekobot.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import wtf.beatrice.hidekobot.entities.CommandRunner;
public interface CommandRunnerRepository extends JpaRepository<CommandRunner, String>
{
}

View File

@@ -0,0 +1,8 @@
package wtf.beatrice.hidekobot.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import wtf.beatrice.hidekobot.entities.PendingDisabledMessage;
public interface PendingDisabledMessageRepository extends JpaRepository<PendingDisabledMessage, String>
{
}

View File

@@ -0,0 +1,8 @@
package wtf.beatrice.hidekobot.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import wtf.beatrice.hidekobot.entities.UrbanDictionaryEntry;
public interface UrbanDictionaryRepository extends JpaRepository<UrbanDictionaryEntry, String>
{
}

View File

@@ -3,35 +3,38 @@ package wtf.beatrice.hidekobot.runnables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.services.CommandService;
import wtf.beatrice.hidekobot.services.DatabaseService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class ExpiredMessageTask implements Runnable {
public class ExpiredMessageTask implements Runnable
{
private final DatabaseService databaseService;
private final CommandService commandService;
private final DateTimeFormatter formatter;
private static final Logger LOGGER = LoggerFactory.getLogger(ExpiredMessageTask.class);
private DatabaseSource databaseSource;
public ExpiredMessageTask()
public ExpiredMessageTask(DatabaseService databaseService,
CommandService commandService)
{
this.databaseService = databaseService;
this.commandService = commandService;
String format = Cache.getExpiryTimestampFormat();
formatter = DateTimeFormatter.ofPattern(format);
databaseSource = Cache.getDatabaseSource();
}
@Override
public void run() {
public void run()
{
databaseSource = Cache.getDatabaseSource();
if(databaseSource == null) return;
List<String> expiringMessages = Cache.getDatabaseSource().getQueuedExpiringMessages();
List<String> expiringMessages = databaseService.getQueuedExpiringMessages();
if (expiringMessages == null || expiringMessages.isEmpty()) return;
LocalDateTime now = LocalDateTime.now();
@@ -41,11 +44,11 @@ public class ExpiredMessageTask implements Runnable {
if (Cache.isVerbose()) LOGGER.info("expired check: {}", messageId);
String expiryTimestamp = databaseSource.getQueuedExpiringMessageExpiryDate(messageId);
String expiryTimestamp = databaseService.getQueuedExpiringMessageExpiryDate(messageId);
if (expiryTimestamp == null || expiryTimestamp.isEmpty()) // if missing timestamp
{
// count it as already expired
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
// move on to next message
continue;
}
@@ -55,7 +58,7 @@ public class ExpiredMessageTask implements Runnable {
if (now.isAfter(expiryDate))
{
if (Cache.isVerbose()) LOGGER.info("expired: {}", messageId);
CommandUtil.disableExpired(messageId);
commandService.disableExpired(messageId);
}
}

View File

@@ -18,7 +18,8 @@ public class HeartBeatTask implements Runnable
String urlString = Cache.getFullHeartBeatLink();
if (urlString == null || urlString.isEmpty()) return;
try {
try
{
URL heartbeatUrl = new URL(urlString);
@@ -32,13 +33,13 @@ public class HeartBeatTask implements Runnable
{
// only log ok response codes when verbosity is enabled
if (Cache.isVerbose()) LOGGER.info("Heartbeat response code: {}", responseCode);
}
else
} else
{
LOGGER.error("Heartbeat returned problematic response code: {}", responseCode);
}
} catch (IOException e) {
} catch (IOException e)
{
LOGGER.error("Error while trying to push heartbeat", e);
}

View File

@@ -22,7 +22,8 @@ public class StatusUpdateTask implements Runnable
);
@Override
public void run() {
public void run()
{
int randomPos = RandomUtil.getRandomNumber(0, statuses.size() - 1);
String status = statuses.get(randomPos) + " | " + Cache.getBotPrefix() + " help";
HidekoBot.getAPI().getPresence().setActivity(Activity.playing(status));

View File

@@ -13,13 +13,18 @@ import wtf.beatrice.hidekobot.objects.comparators.TriviaScoreComparator;
import wtf.beatrice.hidekobot.objects.fun.TriviaCategory;
import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion;
import wtf.beatrice.hidekobot.objects.fun.TriviaScore;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.services.CommandService;
import wtf.beatrice.hidekobot.services.DatabaseService;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
public class TriviaTask implements Runnable
{
private final DatabaseService databaseService;
private final CommandService commandService;
private final User author;
private final MessageChannel channel;
@@ -33,11 +38,17 @@ public class TriviaTask implements Runnable
private int iteration = 0;
public TriviaTask(User author, MessageChannel channel, TriviaCategory category)
public TriviaTask(User author,
MessageChannel channel,
TriviaCategory category,
DatabaseService databaseService,
CommandService commandService)
{
this.author = author;
this.channel = channel;
this.category = category;
this.databaseService = databaseService;
this.commandService = commandService;
triviaJson = Trivia.fetchJson(Trivia.getTriviaLink(category.categoryId()));
questions = Trivia.parseQuestions(triviaJson); //todo: null check, rate limiting...
@@ -55,7 +66,7 @@ public class TriviaTask implements Runnable
if (previousMessage != null)
{
// todo: we shouldn't use this method, since it messes with the database... look at coin reflip
CommandUtil.disableExpired(previousMessage.getId());
commandService.disableExpired(previousMessage.getId());
String previousCorrectAnswer = questions.get(iteration - 1).correctAnswer();
@@ -93,12 +104,14 @@ public class TriviaTask implements Runnable
previousScore = score;
topScore = score;
pos = 1;
} else {
} else
{
if (score != previousScore) pos++;
}
if (pos == 1) winners.add(user);
else {
else
{
othersBuilder.append("\n").append(pos)
.append(" | ").append(user)
.append(": ").append(score).append(" points");
@@ -113,7 +126,8 @@ public class TriviaTask implements Runnable
if (i + 1 != winners.size())
{
winnersBuilder.append(", "); // separate with comma except on last run
} else {
} else
{
winnersBuilder.append(": ").append(topScore).append(" points \uD83C\uDF89");
}
}
@@ -190,12 +204,12 @@ public class TriviaTask implements Runnable
.complete();
Cache.getDatabaseSource().trackRanCommandReply(previousMessage, author);
databaseService.trackRanCommandReply(previousMessage, author);
// todo: ^ we should get rid of this tracking, since we don't need to know who started the trivia.
// todo: however, for now, that's the only way to avoid a thread-locking scenario as some data is
// todo: only stored in that table. this should be solved when we merge / fix the two main tables.
// todo: then, we can remove this instruction.
Cache.getDatabaseSource().queueDisabling(previousMessage);
databaseService.queueDisabling(previousMessage);
iteration++;
}

View File

@@ -1,4 +1,4 @@
package wtf.beatrice.hidekobot.util;
package wtf.beatrice.hidekobot.services;
import net.dv8tion.jda.api.JDA;
@@ -14,21 +14,26 @@ import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.RestAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.objects.commands.SlashCommand;
import java.util.ArrayList;
import java.util.List;
public class CommandUtil
@Component
public class CommandService
{
private static final Logger LOGGER = LoggerFactory.getLogger(CommandUtil.class);
private static final Logger LOGGER = LoggerFactory.getLogger(CommandService.class);
private CommandUtil() {
throw new IllegalStateException("Utility class");
private final DatabaseService databaseService;
public CommandService(@Autowired DatabaseService databaseService)
{
this.databaseService = databaseService;
}
/**
@@ -37,17 +42,37 @@ public class CommandUtil
*
* @param event the button interaction event.
*/
public static void delete(ButtonInteractionEvent event)
public void deleteUserLinkedMessage(ButtonInteractionEvent event)
{
// check if the user interacting is the same one who ran the command
if (!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) {
if (!databaseService.isUserTrackedFor(event.getUser().getId(), event.getMessageId()))
{
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// delete the message
event.getInteraction().getMessage().delete().queue();
// no need to manually untrack it from database, it will be purged on the next planned check.
// Acknowledge immediately so the interaction token stays valid
event.deferEdit().queue(hook -> {
// Try deleting via the interaction webhook (works for original interaction responses)
hook.deleteOriginal().queue(
success -> { /* optional: databaseService.untrackExpiredMessage(event.getMessageId()); */ },
failure -> {
// Fallback to channel delete (works even if webhook token expired)
event.getChannel().deleteMessageById(event.getMessageId()).queue(
null,
__ -> { /* ignore if already deleted */ }
);
}
);
},
failure -> {
// If we failed to acknowledge (interaction already expired), try channel delete anyway
event.getChannel().deleteMessageById(event.getMessageId()).queue(
null,
__ -> { /* ignore if already deleted */ }
);
}
);
}
@@ -58,13 +83,15 @@ public class CommandUtil
*
* @param force a boolean specifying if the update should be forced even if no differences were found.
*/
public static void updateSlashCommands(boolean force)
public void updateSlashCommands(boolean force)
{
// populate commands list from registered commands
List<CommandData> allCommands = new ArrayList<>();
for (SlashCommand cmd : Cache.getSlashCommandListener().getRegisteredCommands())
{ allCommands.add(cmd.getSlashCommandData()); }
{
allCommands.add(cmd.getSlashCommandData());
}
JDA jdaInstance = HidekoBot.getAPI();
@@ -156,49 +183,46 @@ public class CommandUtil
*
* @param messageId the message id to disable.
*/
public static void disableExpired(String messageId)
public void disableExpired(String messageId)
{
DatabaseSource databaseSource = Cache.getDatabaseSource();
String channelId = databaseSource.getQueuedExpiringMessageChannel(messageId);
String channelId = databaseService.getQueuedExpiringMessageChannel(messageId);
// todo: warning, the following method + related if check are thread-locking.
// todo: we should probably merge the two tables somehow, since they have redundant information.
ChannelType msgChannelType = databaseSource.getTrackedMessageChannelType(messageId);
ChannelType msgChannelType = databaseService.getTrackedMessageChannelType(messageId);
MessageChannel textChannel = null;
// this should never happen, but only message channels are supported.
if (!msgChannelType.isMessage())
{
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
return;
}
// if this is a DM
if (!(msgChannelType.isGuild()))
{
String userId = databaseSource.getTrackedReplyUserId(messageId);
String userId = databaseService.getTrackedReplyUserId(messageId);
User user = userId == null ? null : HidekoBot.getAPI().retrieveUserById(userId).complete();
if (user == null)
{
// if user is not found, consider it expired
// (deleted profile, or blocked the bot)
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
return;
}
textChannel = user.openPrivateChannel().complete();
}
else
} else
{
String guildId = databaseSource.getQueuedExpiringMessageGuild(messageId);
String guildId = databaseService.getQueuedExpiringMessageGuild(messageId);
Guild guild = guildId == null ? null : HidekoBot.getAPI().getGuildById(guildId);
if (guild == null)
{
// if guild is not found, consider it expired
// (server was deleted or bot was kicked)
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
return;
}
textChannel = guild.getTextChannelById(channelId);
@@ -208,7 +232,7 @@ public class CommandUtil
{
// if channel is not found, count it as expired
// (channel was deleted or bot permissions restricted)
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
return;
}
@@ -221,7 +245,7 @@ public class CommandUtil
message -> {
if (message == null)
{
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
return;
}
@@ -234,9 +258,9 @@ public class CommandUtil
}
message.editMessageComponents(newComponents).queue();
databaseSource.untrackExpiredMessage(messageId);
databaseService.untrackExpiredMessage(messageId);
},
error -> databaseSource.untrackExpiredMessage(messageId));
error -> databaseService.untrackExpiredMessage(messageId));
}
}

View File

@@ -0,0 +1,183 @@
package wtf.beatrice.hidekobot.services;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.entities.CommandRunner;
import wtf.beatrice.hidekobot.entities.PendingDisabledMessage;
import wtf.beatrice.hidekobot.entities.UrbanDictionaryEntry;
import wtf.beatrice.hidekobot.repositories.CommandRunnerRepository;
import wtf.beatrice.hidekobot.repositories.PendingDisabledMessageRepository;
import wtf.beatrice.hidekobot.repositories.UrbanDictionaryRepository;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service
@Transactional
public class DatabaseService
{
private final PendingDisabledMessageRepository pendingRepo;
private final CommandRunnerRepository runnerRepo;
private final UrbanDictionaryRepository urbanRepo;
public DatabaseService(PendingDisabledMessageRepository p, CommandRunnerRepository c, UrbanDictionaryRepository u)
{
this.pendingRepo = p;
this.runnerRepo = c;
this.urbanRepo = u;
}
// trackRanCommandReply
public void trackRanCommandReply(Message message, User user)
{
String userId = user.getId();
String guildId = message.getChannelType().isGuild() ? message.getGuild().getId() : userId;
CommandRunner row = new CommandRunner();
row.setMessageId(message.getId());
row.setGuildId(guildId);
row.setChannelId(message.getChannel().getId());
row.setUserId(userId);
row.setChannelType(message.getChannelType().name());
runnerRepo.save(row);
}
public boolean isUserTrackedFor(String userId, String messageId)
{
return runnerRepo.findById(messageId)
.map(r -> userId.equals(r.getUserId()))
.orElse(false);
}
public ChannelType getTrackedMessageChannelType(String messageId)
{
return runnerRepo.findById(messageId)
.map(r -> ChannelType.valueOf(r.getChannelType()))
.orElse(null);
}
public String getTrackedReplyUserId(String messageId)
{
return runnerRepo.findById(messageId)
.map(CommandRunner::getUserId)
.orElse(null);
}
public void queueDisabling(Message message)
{
String guildId = message.getChannelType().isGuild() ? message.getGuild().getId() : "PRIVATE";
LocalDateTime expiry = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds());
String formatted = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat()).format(expiry);
PendingDisabledMessage row = new PendingDisabledMessage();
row.setMessageId(message.getId());
row.setChannelId(message.getChannel().getId());
row.setGuildId(guildId);
row.setExpiryTimestamp(formatted);
pendingRepo.save(row);
}
public List<String> getQueuedExpiringMessages()
{
return pendingRepo.findAll()
.stream()
.map(PendingDisabledMessage::getMessageId)
.toList();
}
public void untrackExpiredMessage(String messageId)
{
pendingRepo.deleteById(messageId);
runnerRepo.deleteById(messageId);
urbanRepo.deleteById(messageId);
}
public String getQueuedExpiringMessageExpiryDate(String messageId)
{
return pendingRepo.findById(messageId).map(PendingDisabledMessage::getExpiryTimestamp).orElse(null);
}
public String getQueuedExpiringMessageChannel(String messageId)
{
return pendingRepo.findById(messageId).map(PendingDisabledMessage::getChannelId).orElse(null);
}
public String getQueuedExpiringMessageGuild(String messageId)
{
return pendingRepo.findById(messageId).map(PendingDisabledMessage::getGuildId).orElse(null);
}
public void trackUrban(String meanings, String examples, String contributors, String dates, Message message, String term)
{
UrbanDictionaryEntry e = new UrbanDictionaryEntry();
e.setMessageId(message.getId());
e.setPage(0);
e.setMeanings(meanings);
e.setExamples(examples);
e.setContributors(contributors);
e.setDates(dates);
e.setTerm(term);
urbanRepo.save(e);
}
public int getUrbanPage(String messageId)
{
return urbanRepo.findById(messageId)
.map(UrbanDictionaryEntry::getPage)
.orElse(0);
}
public String getUrbanMeanings(String messageId)
{
return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getMeanings).orElse(null);
}
public String getUrbanExamples(String messageId)
{
return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getExamples).orElse(null);
}
public String getUrbanContributors(String messageId)
{
return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getContributors).orElse(null);
}
public String getUrbanDates(String messageId)
{
return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getDates).orElse(null);
}
public String getUrbanTerm(String messageId)
{
return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getTerm).orElse(null);
}
public void setUrbanPage(String messageId, int page)
{
urbanRepo.findById(messageId).ifPresent(e -> {
e.setPage(page);
urbanRepo.save(e);
});
}
public void resetExpiryTimestamp(String messageId)
{
pendingRepo.findById(messageId).ifPresent(row -> {
String formatted = DateTimeFormatter
.ofPattern(Cache.getExpiryTimestampFormat())
.format(LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds()));
row.setExpiryTimestamp(formatted);
pendingRepo.save(row);
});
}
}

View File

@@ -12,7 +12,8 @@ import java.util.Arrays;
public class FormatUtil
{
private FormatUtil() {
private FormatUtil()
{
throw new IllegalStateException("Utility class");
}
@@ -60,21 +61,25 @@ public class FormatUtil
*
* @return the formatted String
*/
public static String getNiceDuration(Duration duration) {
public static String getNiceDuration(Duration duration)
{
long days = duration.toDays();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
long seconds = duration.toSecondsPart();
StringBuilder sb = new StringBuilder();
if (days > 0) {
if (days > 0)
{
sb.append(days).append("d ");
sb.append(hours).append("h ");
sb.append(minutes).append("m ");
} else if (hours > 0) {
} else if (hours > 0)
{
sb.append(hours).append("h ");
sb.append(minutes).append("m ");
} else if (minutes > 0) {
} else if (minutes > 0)
{
sb.append(minutes).append("m ");
}
sb.append(seconds).append("s");

View File

@@ -1,70 +0,0 @@
package wtf.beatrice.hidekobot.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Deprecated(since = "0.5.16", forRemoval = true)
public class Logger<T>
{
// objects that we need to have for a properly formatted message
private final String className;
private final String format = "[%date% %time%] [%class%] %message%";
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
// when initializing a new logger, save variables in that instance
public Logger(Class<T> logClass)
{
className = logClass.getSimpleName();
}
/**
* Logs a message to console, following a specific format.
*
* @param message the message to log
*/
public void log(String message)
{
LocalDateTime now = LocalDateTime.now();
String currentDate = dateFormatter.format(now);
String currentTime = timeFormatter.format(now);
logRaw(format
.replace("%date%", currentDate)
.replace("%time%", currentTime)
.replace("%class%", className)
.replace("%message%", message));
}
/**
* Logs a message to console, after delaying it.
*
* @param message the message to log
* @param delay the time to wait before logging, in seconds
*/
public void log(String message, int delay)
{
// create a new scheduled executor with an anonymous runnable...
//... after waiting <delay> seconds.
try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor()) {
executor.schedule(() -> log(message), delay, TimeUnit.SECONDS);
}
}
/**
* Prints a message to console without any formatting.
*
* @param message the message to log
*/
public void logRaw(String message)
{
System.out.println(message);
}
}

View File

@@ -10,7 +10,8 @@ import java.util.Random;
public class RandomUtil
{
private RandomUtil() {
private RandomUtil()
{
throw new IllegalStateException("Utility class");
}
@@ -47,7 +48,8 @@ public class RandomUtil
}
public static Random getRandom() {
public static Random getRandom()
{
return randomInstance;
}

View File

@@ -10,33 +10,38 @@ import java.util.List;
public class SerializationUtil
{
private SerializationUtil() {
private SerializationUtil()
{
throw new IllegalStateException("Utility class");
}
public static <T> String serializeBase64(List<T> dataList) {
public static <T> String serializeBase64(List<T> dataList)
{
try (ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream so = new ObjectOutputStream(bo)) {
ObjectOutputStream so = new ObjectOutputStream(bo))
{
so.writeObject(dataList);
so.flush();
return Base64.getEncoder().encodeToString(bo.toByteArray());
}
catch (IOException e) {
} catch (IOException e)
{
throw new SerializationException("Error during serialization", e);
}
}
public static <T> LinkedList<T> deserializeBase64(String dataStr) {
public static <T> LinkedList<T> deserializeBase64(String dataStr)
{
byte[] b = Base64.getDecoder().decode(dataStr);
ByteArrayInputStream bi = new ByteArrayInputStream(b);
ObjectInputStream si;
try {
try
{
si = new ObjectInputStream(bi);
return LinkedList.class.cast(si.readObject());
}
catch (IOException | ClassNotFoundException e) {
} catch (IOException | ClassNotFoundException e)
{
throw new SerializationException("Error during deserialization", e);
}
}

View File

@@ -0,0 +1,8 @@
package wtf.beatrice.hidekobot.util;
import wtf.beatrice.hidekobot.services.CommandService;
import wtf.beatrice.hidekobot.services.DatabaseService;
public record Services(CommandService commandService, DatabaseService databaseService)
{
}

View File

@@ -0,0 +1,8 @@
spring.datasource.url=jdbc:sqlite:${APP_HOME}/db.sqlite
spring.datasource.driver-class-name=org.sqlite.JDBC
# let Hibernate create/update tables for you during the migration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
# optional logging while migrating
#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
<!--
<suppress>
<notes><![CDATA[
file name: snakeyaml-1.33.jar
@@ -7,25 +8,5 @@
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
<cve>CVE-2021-4235</cve>
</suppress>
<suppress>
<notes><![CDATA[
file name: snakeyaml-1.33.jar
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
<cve>CVE-2022-3064</cve>
</suppress>
<suppress>
<notes><![CDATA[
file name: snakeyaml-1.33.jar
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
<vulnerabilityName>CVE-2022-1471</vulnerabilityName>
</suppress>
<suppress>
<notes><![CDATA[
file name: json-20220924.jar
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.json/json@.*$</packageUrl>
<vulnerabilityName>CVE-2022-45688</vulnerabilityName>
</suppress>
-->
</suppressions>