diff --git a/.cz.toml b/.cz.toml
new file mode 100644
index 0000000..946a79f
--- /dev/null
+++ b/.cz.toml
@@ -0,0 +1,13 @@
+[tool.commitizen]
+name = "cz_conventional_commits"
+tag_format = "$version"
+version_scheme = "semver"
+version = "2.0.1"
+update_changelog_on_bump = true
+major_version_zero = false
+pre_bump_hooks = [
+ "bash ci-scripts/prepare_java_version.sh"
+]
+post_bump_hooks = [
+ "bash ci-scripts/commit_java_version.sh"
+]
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..81b3a9a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,13 @@
+NETNEUTRALITY_BOT_ENV=dev
+NETNEUTRALITY_BOT_TOKEN=
+NETNEUTRALITY_BOT_PREFIX=$
+NETNEUTRALITY_BOT_OWNER=
+NETNEUTRALITY_API_PORT=9000
+
+MYSQL_HOST=127.0.0.1
+MYSQL_USER=root
+MYSQL_PASSWORD=
+MYSQL_DATABASE=
+MYSQL_PORT=3306
+
+NETNEUTRALITY_SENTRY_DSN=
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d8fc794..d7ff51c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,9 @@
# BlueJ files
*.ctxt
+# Config file
+bot.properties
+
# Mobile Tools for Java (J2ME)
.mtj.tmp/
@@ -37,3 +40,8 @@ hs_err_pid*
# Maven
log/
target/
+
+#log4j
+logs/
+
+.env
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7edde83..ac04551 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,20 +1,54 @@
-image: maven:latest
+image: eclipse-temurin:17-jdk-alpine
variables:
- MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
- MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+ MAVEN_OPTS: >-
+ -Dhttps.protocols=TLSv1.2
+ -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository
+ -Dorg.slf4j.simpleLogger.showDateTime=true
+ -Djava.awt.headless=true
+
+ MAVEN_CLI_OPTS: >-
+ --batch-mode
+ --errors
+ --fail-at-end
+ --show-version
+ --no-transfer-progress
+ -DinstallAtEnd=true
+ -DdeployAtEnd=true
cache:
+ key: "maven-$CI_COMMIT_REF_SLUG"
paths:
- - .m2/repository/
- - target/
+ - .m2/repository
-build:
- stage: build
+stages:
+ - compile
+ - test
+ - package
+
+compile:
+ stage: compile
script:
- - "mvn --batch-mode compile"
+ - "chmod +x ./mvnw"
+ - "./mvnw $MAVEN_CLI_OPTS compile"
test:
stage: test
script:
- - "mvn --batch-mode test"
+ - "chmod +x ./mvnw"
+ - "./mvnw $MAVEN_CLI_OPTS test"
+ artifacts:
+ when: always
+ reports:
+ junit:
+ - "**/target/*-reports/TEST-*.xml"
+
+package:
+ stage: package
+ script:
+ - "chmod +x ./mvnw"
+ - "./mvnw $MAVEN_CLI_OPTS package -DskipTests=true"
+ artifacts:
+ name: "Maven artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
+ paths:
+ - "**/target"
\ No newline at end of file
diff --git a/.jpb/jpb-settings.xml b/.jpb/jpb-settings.xml
new file mode 100644
index 0000000..935cf0d
--- /dev/null
+++ b/.jpb/jpb-settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..346d645
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8a02ef4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,29 @@
+## 2.0.1 (2024-07-14)
+
+### Refactor
+
+- fix inspection errors for deprecated calls and concatenation
+
+## 2.0.0 (2024-07-14)
+
+### BREAKING CHANGE
+
+- removed TrackLoader amd LoadableSongs
+
+### Feat
+
+- **Settings**: add helpers to get version number of bot
+
+### Fix
+
+- fix relection package to new path
+- fix wrongly named environment variables
+- bump youtube-source to 1.4.0
+
+### Refactor
+
+- Remove unused command
+- add release to Sentry config
+- remove convoluted TrackLoader and LoadableSongs abstraction
+- **LogHandler**: use the exception itself instead of a string for buildSentryErrorMessage
+- change package path to reflect group id of pom.xml
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..cc852a0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,25 @@
+FROM eclipse-temurin:17-jdk-alpine as build
+
+COPY .mvn/ .mvn
+COPY mvnw pom.xml ./
+COPY src ./src
+
+RUN ["chmod", "+x", "./mvnw"]
+RUN ./mvnw install
+
+
+FROM eclipse-temurin:17-jre-alpine
+
+RUN apk add netcat-openbsd
+RUN apk add curl
+
+WORKDIR /app
+
+ARG API_PORT=9000
+EXPOSE ${API_PORT}
+
+COPY startup.sh ./
+COPY --from=build /target/netneutrality-jar-with-dependencies.jar ./
+
+RUN ["chmod", "+x", "./startup.sh"]
+CMD ["./startup.sh"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index b7687ab..f288702 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
- Copyright (C) 2007 Free Software Foundation, Inc.
+ Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
- netneutrality
- Copyright (C) 2018 Anthony
+
+ Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -645,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program. If not, see .
+ along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
- netneutrality Copyright (C) 2018 Anthony
+ Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
-.
+.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
-.
+.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9aff4d0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# Net Neutrality Discord bot
+
+This is the source code for my custom discord bot.
+This is a personal project, so I offer no support on how to build it.
+
+
+The preferred method for running the bot is by using Docker Compose like this:
+
+```bash
+docker compose build
+docker compose up -d
+```
+
+The environment variables needed for the bot to run are available in the file **bot.properties.example**
+If running locally without Docker, you need to copy the example file to **bot.properties** and fill the values.
\ No newline at end of file
diff --git a/bot.properties.example b/bot.properties.example
new file mode 100644
index 0000000..fe1be76
--- /dev/null
+++ b/bot.properties.example
@@ -0,0 +1,13 @@
+NETNEUTRALITY_BOT_ENV=dev
+NETNEUTRALITY_BOT_TOKEN=
+NETNEUTRALITY_BOT_PREFIX=
+NETNEUTRALITY_BOT_OWNER=
+NETNEUTRALITY_API_PORT=9000
+
+MYSQL_HOST=127.0.0.1
+MYSQL_USER=root
+MYSQL_PASSWORD=
+MYSQL_DATABASE=
+MYSQL_PORT=3306
+
+NETNEUTRALITY_SENTRY_DSN=
\ No newline at end of file
diff --git a/build-bot.sh b/build-bot.sh
new file mode 100644
index 0000000..854e33b
--- /dev/null
+++ b/build-bot.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+git fetch || exit 1
+git pull || exit 1
+
+docker compose down || exit 1
+docker compose pull || exit 1
+docker compose build || exit 1
+
+docker compose up -d
+docker compose logs -f
\ No newline at end of file
diff --git a/ci-scripts/commit_java_version.sh b/ci-scripts/commit_java_version.sh
new file mode 100644
index 0000000..fedd27e
--- /dev/null
+++ b/ci-scripts/commit_java_version.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+./mvnw versions:commit
diff --git a/ci-scripts/prepare_java_version.sh b/ci-scripts/prepare_java_version.sh
new file mode 100644
index 0000000..7e41dea
--- /dev/null
+++ b/ci-scripts/prepare_java_version.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+./mvnw versions:set -DnewVersion=$CZ_PRE_NEW_VERSION
diff --git a/docker-compose-webapp.yml b/docker-compose-webapp.yml
new file mode 100644
index 0000000..bd39fd7
--- /dev/null
+++ b/docker-compose-webapp.yml
@@ -0,0 +1,64 @@
+services:
+ db:
+ image: mariadb:lts
+ container_name: db
+ restart: unless-stopped
+ volumes:
+ - netneutrality-db:/var/lib/mysql
+ - ./sql:/docker-entrypoint-initdb.d
+ environment:
+ MYSQL_USER: 'java'
+ MYSQL_PASSWORD: 'password'
+ MYSQL_DATABASE: 'netneutrality'
+ MYSQL_ROOT_PASSWORD: 'password'
+ MARIADB_AUTO_UPGRADE: '1'
+ ports:
+ - "3306"
+ networks:
+ - internal
+
+ bot:
+ container_name: bot
+ build: ./
+ restart: unless-stopped
+ depends_on:
+ - db
+ volumes:
+ - ./logs:/app/logs
+ environment:
+ MYSQL_HOST: 'db'
+ MYSQL_USER: 'java'
+ MYSQL_PASSWORD: 'password'
+ MYSQL_DATABASE: 'netneutrality'
+ ports:
+ - "9000:9000"
+ networks:
+ - internal
+
+ webapp:
+ image: bot-webapp:dev
+ build: ../netneutrality-webapp
+ restart: always
+ depends_on:
+ - db
+ - bot
+ volumes:
+ - ./logs:/app/logs
+ environment:
+ DB_CONNECTION: 'mysql'
+ DB_HOST: 'db'
+ DB_PORT: '3306'
+ DB_DATABASE: 'netneutrality'
+ DB_USERNAME: 'java'
+ DB_PASSWORD: 'password'
+ BOT_URL: 'http://bot:9000'
+ ports:
+ - '8080:80'
+ networks:
+ - internal
+
+volumes:
+ netneutrality-db:
+
+networks:
+ internal:
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..19e1c1b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,43 @@
+services:
+ db:
+ image: mariadb:lts
+ container_name: db
+ restart: unless-stopped
+ volumes:
+ - netneutrality-db:/var/lib/mysql
+ - ./sql:/docker-entrypoint-initdb.d
+ environment:
+ MYSQL_USER: 'java'
+ MYSQL_PASSWORD: 'password'
+ MYSQL_DATABASE: 'netneutrality'
+ MYSQL_ROOT_PASSWORD: 'password'
+ MARIADB_AUTO_UPGRADE: '1'
+ ports:
+ - "3306"
+ networks:
+ - internal
+
+ bot:
+ container_name: bot
+ build: ./
+ restart: unless-stopped
+ depends_on:
+ - db
+ volumes:
+ - ./logs:/app/logs
+ environment:
+ MYSQL_HOST: 'db'
+ MYSQL_USER: 'java'
+ MYSQL_PASSWORD: 'password'
+ MYSQL_DATABASE: 'netneutrality'
+ env_file: ".env"
+ ports:
+ - "9000:9000"
+ networks:
+ - internal
+
+volumes:
+ netneutrality-db:
+
+networks:
+ internal:
\ No newline at end of file
diff --git a/lib/jansi-1.17.jar b/lib/jansi-1.17.jar
deleted file mode 100644
index dceada8..0000000
Binary files a/lib/jansi-1.17.jar and /dev/null differ
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..8d937f4
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..c4586b5
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
index 99df7a5..8a2db6d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,84 +1,187 @@
+
+
- 4.0.0
- com.xtremcommunity.discord
- netneutrality
- 0.0.1
- MusicBot
- Music bot
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
-
- 1.8
- 1.8
-
+ me.bobert
+ netneutrality
+ 2.0.1
+ netneutrality-discord-bot
+ Discord bot
+ jar
-
- https://gitlab.xtremcommunity.com/nn.jar
-
+
+ me.bobert.netneutrality.NetNeutrality
+ 17
+ UTF-8
+ 17
+ 17
+
-
-
- jcenter
- http://jcenter.bintray.com
-
+
+
+
+ src/main/resources
+ true
+
+
-
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.3.1
+
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+
+ true
+
+
+ true
+ ${main.class}
+
+
+
+ jar-with-dependencies
+
+ netneutrality
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
-
-
- net.dv8tion
- JDA
- 3.5.0_327
-
+
+
+ central
+ https://repo.maven.apache.org/maven2/
+
-
- com.squareup.okhttp3
- okhttp
- 3.9.1
-
+
+ jitpack
+ https://jitpack.io
+
-
- org.reflections
- reflections
- 0.9.11
-
+
+ arbjergDev-releases
+ Lavalink Repository
+ https://maven.lavalink.dev/releases
+
+
+
+
+
+
+ net.dv8tion
+ JDA
+ 5.0.0
+
+
+ dev.arbjerg
+ lavaplayer
+ 2.1.2
+
+
+ dev.lavalink.youtube
+ v2
+ 1.4.0
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+
+ org.hibernate.orm
+ hibernate-core
+ 6.1.7.Final
+
+
+ org.hibernate.orm
+ hibernate-hikaricp
+ 6.1.7.Final
+
+
+ com.mysql
+ mysql-connector-j
+ 8.3.0
+
+
+
+ io.sentry
+ sentry
+ 7.6.0
+
+
+
+ org.json
+ json
+ 20240303
+
+
+
+ org.reflections
+ reflections
+ 0.10.2
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.23.1
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.23.1
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ 2.23.1
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ commons-codec
+ commons-codec
+ 1.15
+
+
+ commons-io
+ commons-io
+ 2.16.1
+
-
- mysql
- mysql-connector-java
- 5.1.45
-
-
- org.apache.logging.log4j
- log4j-api
- 2.10.0
-
-
- org.apache.logging.log4j
- log4j-core
- 2.10.0
-
-
- org.apache.logging.log4j
- log4j-slf4j-impl
- 2.10.0
-
-
- org.fusesource.jansi
- jansi
- 1.17
- runtime
-
-
- com.sedmelluq
- lavaplayer
- 1.2.51
- junitjunit
- RELEASE
+ 4.13.2
+
\ No newline at end of file
diff --git a/sql/netneutrality_jpa.sql b/sql/netneutrality_jpa.sql
new file mode 100644
index 0000000..61976ad
--- /dev/null
+++ b/sql/netneutrality_jpa.sql
@@ -0,0 +1,163 @@
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+START TRANSACTION;
+SET time_zone = "+00:00";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8mb4 */;
+
+--
+-- Database: `netneutrality`
+--
+CREATE DATABASE IF NOT EXISTS netneutrality CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+USE netneutrality;
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `blacklist`
+--
+
+DROP TABLE IF EXISTS `blacklist`;
+CREATE TABLE IF NOT EXISTS `blacklist` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `word` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `enabled` tinyint(1) NOT NULL,
+ `creator_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `fk_blacklist_guilds` (`guild_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `guilds`
+--
+
+DROP TABLE IF EXISTS `guilds`;
+CREATE TABLE IF NOT EXISTS `guilds` (
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `logging` tinyint(1) NOT NULL DEFAULT 0,
+ `debug` tinyint(1) NOT NULL DEFAULT 1,
+ `log_channel` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`guild_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `guild_settings`
+--
+
+DROP TABLE IF EXISTS `guild_settings`;
+CREATE TABLE IF NOT EXISTS `guild_settings` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `value` text COLLATE utf8mb4_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `fk_guildsetting_guilds` (`guild_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `logging`
+--
+
+DROP TABLE IF EXISTS `logging`;
+CREATE TABLE IF NOT EXISTS `logging` (
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `channel_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `enabled` tinyint(1) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`guild_id`,`channel_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `log_message`
+--
+
+DROP TABLE IF EXISTS `log_message`;
+CREATE TABLE IF NOT EXISTS `log_message` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `channel_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `message_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `user_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `date_sent` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
+ `message` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `edited_message` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `message_id` (`message_id`),
+ KEY `fk_log_guilds` (`guild_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `permissions`
+--
+
+DROP TABLE IF EXISTS `permissions`;
+CREATE TABLE IF NOT EXISTS `permissions` (
+ `guild_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `role_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `type` int(11) DEFAULT NULL,
+ `command` int(11) DEFAULT NULL,
+ PRIMARY KEY (`guild_id`,`role_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `web_users`
+--
+
+DROP TABLE IF EXISTS `web_users`;
+CREATE TABLE IF NOT EXISTS `web_users` (
+ `id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `email` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
+ `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `expires` timestamp NOT NULL DEFAULT current_timestamp(),
+ `refresh_token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+--
+-- Constraints for dumped tables
+--
+
+--
+-- Constraints for table `blacklist`
+--
+ALTER TABLE `blacklist`
+ ADD CONSTRAINT `fk_blacklist_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `guild_settings`
+--
+ALTER TABLE `guild_settings`
+ ADD CONSTRAINT `fk_guildsetting_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `logging`
+--
+ALTER TABLE `logging`
+ ADD CONSTRAINT `fk_logging_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+--
+-- Constraints for table `log_message`
+--
+ALTER TABLE `log_message`
+ ADD CONSTRAINT `fk_log_guilds` FOREIGN KEY (`guild_id`) REFERENCES `guilds` (`guild_id`) ON DELETE CASCADE ON UPDATE CASCADE;
+COMMIT;
+
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
diff --git a/src/main/java/me/bobert/netneutrality/NetNeutrality.java b/src/main/java/me/bobert/netneutrality/NetNeutrality.java
new file mode 100644
index 0000000..3f37905
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/NetNeutrality.java
@@ -0,0 +1,119 @@
+package me.bobert.netneutrality;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Sentry;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.JDABuilder;
+import net.dv8tion.jda.api.entities.Activity;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.exceptions.InvalidTokenException;
+import net.dv8tion.jda.api.requests.GatewayIntent;
+import me.bobert.netneutrality.command.CommandManager;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.db.DBController;
+import me.bobert.netneutrality.hooks.SlashCommandHooks;
+import me.bobert.netneutrality.hooks.guilds.ServerJoinHandler;
+import me.bobert.netneutrality.hooks.message.EditHandler;
+import me.bobert.netneutrality.hooks.message.MessageReceivedHandler;
+import me.bobert.netneutrality.http.server.WebApi;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.thread.BaseThreadFactory;
+import me.bobert.netneutrality.thread.ShutdownThread;
+import me.bobert.netneutrality.thread.ThreadManager;
+import me.bobert.netneutrality.utils.Settings;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configurator;
+
+public class NetNeutrality {
+
+ private static JDA jda;
+ public final static Logger logger = LogManager.getLogger(NetNeutrality.class);
+ public static final String VERSION = Settings.getInstance().getMavenVersion();
+
+ public static void main(String[] args) {
+ String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
+
+ String sentryDsn = Settings.getInstance().get("NETNEUTRALITY_SENTRY_DSN");
+ if (sentryDsn != null) {
+ logger.info("Found Sentry DSN in config, setting up the SDK..");
+
+ Sentry.init(options -> {
+ options.setDsn(sentryDsn);
+ options.setEnvironment(env);
+ options.setTracesSampleRate(1.0);
+ options.setDebug(false);
+ options.setRelease(VERSION);
+ });
+ }
+
+ if ("dev".equals(env)) {
+ Configurator.setLevel("net.dv8tion.jda", Level.DEBUG);
+ }
+
+ DBController.init();
+ initJDA();
+ CommandManager.init();
+ MusicManager.initPlayerManager();
+ jda.addEventListener(new MessageReceivedHandler(), new EditHandler(), new ServerJoinHandler());
+ jda.addEventListener(new SlashCommandHooks());
+ new WebApi(Integer.parseInt(Settings.getInstance().get("NETNEUTRALITY_API_PORT", "9000")));
+ jda.getPresence().setActivity(Activity.watching("you"));
+
+ Runtime.getRuntime().addShutdownHook(new BaseThreadFactory("shutdown").newThread(new ShutdownThread()));
+
+ }
+
+ public static void initJDA() {
+ String token = Settings.getInstance().get("NETNEUTRALITY_BOT_TOKEN");
+ if (token == null) {
+ DBController.shutdown();
+ throw new RuntimeException("No bot token provided, exiting.");
+ }
+
+ try {
+ jda = JDABuilder
+ .createDefault(token)
+ .enableIntents(GatewayIntent.GUILD_MEMBERS, GatewayIntent.GUILD_MESSAGE_REACTIONS, GatewayIntent.GUILD_VOICE_STATES, GatewayIntent.MESSAGE_CONTENT)
+ .setActivity(Activity.watching("me load..."))
+ .build();
+ jda.awaitReady();
+ } catch (InvalidTokenException | IllegalArgumentException | InterruptedException e) {
+ LogHandler.log(e);
+ System.exit(1);
+ }
+ }
+
+ public static void shutdown() {
+ System.out.println("[Net Neutrality] Shutting down...");
+ MusicManager.disconnectPlayers();
+ ThreadManager.shutdownExecutor();
+ disconnect();
+ DBController.shutdown();
+ System.out.println("[Net Neutrality] Shutdown complete");
+ }
+
+ public static JDA getJDA() {
+ return jda;
+ }
+
+ public static User getSelf() {
+ return jda.getSelfUser();
+
+ }
+
+ private static void disconnect() {
+ if (jda != null) {
+ jda.shutdown();
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java
new file mode 100644
index 0000000..f02d43b
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/AbstractCommand.java
@@ -0,0 +1,43 @@
+package me.bobert.netneutrality.command;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
+
+public abstract class AbstractCommand implements ICommand {
+
+ @Override
+ public boolean isSlashCommand() {
+ return true;
+ }
+
+ @Override
+ public DefaultMemberPermissions getSlashCommandPermission() {
+ return DefaultMemberPermissions.ENABLED;
+ }
+
+ @Override
+ public boolean canBeExecutedBy(Guild guild, Member member) {
+ return true;
+ }
+
+ @Override
+ public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) {
+ return true;
+ }
+
+ @Deprecated
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ // Prefer slash commands
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java b/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java
new file mode 100644
index 0000000..c59f7a3
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/AbstractMusicCommand.java
@@ -0,0 +1,69 @@
+package me.bobert.netneutrality.command;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.db.GSettings;
+import me.bobert.netneutrality.utils.UserFunction;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Role;
+import net.dv8tion.jda.api.entities.channel.ChannelType;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+
+public abstract class AbstractMusicCommand extends AbstractCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return false;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return true;
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.MUSIC;
+ }
+
+ @Override
+ public boolean canBeExecutedBy(Guild guild, Member member) {
+ String roleDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE);
+
+ if (roleDb != null && !roleDb.isEmpty()) {
+ if (UserFunction.isAdmin(member)) {
+ return true;
+ }
+
+ Role neededRole = guild.getRoleById(roleDb);
+ if (neededRole == null) {
+ // Role doesn't exist on guild anymore, remove from db
+ GuildManager.getManager(guild).getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
+ return true;
+ }
+
+ return member.getRoles().contains(neededRole);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean canBeExecutedFrom(Guild guild, MessageChannel channel) {
+ if (channel.getType() != ChannelType.TEXT)
+ return false;
+
+ String channelDb = GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL);
+
+ if (channelDb != null && !channelDb.isEmpty()) {
+ return channelDb.equals(channel.getId());
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java b/src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java
new file mode 100644
index 0000000..45a2f4a
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/CommandExecutionResult.java
@@ -0,0 +1,64 @@
+package me.bobert.netneutrality.command;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+public class CommandExecutionResult {
+
+ public enum Result {
+ SUCCESS, INFO, WARN, ERROR, NOACTION
+ }
+
+ private final Result result;
+ private final String message;
+ private final Exception exception;
+
+ private CommandExecutionResult(Result result, String message, Exception exception) {
+ this.result = result;
+ this.message = message;
+ this.exception = exception;
+ }
+
+ private CommandExecutionResult(Result result, String message) {
+ this(result, message, null);
+ }
+
+ public Result getResult() {
+ return result;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ public static CommandExecutionResult success(String message) {
+ return new CommandExecutionResult(Result.SUCCESS, message);
+ }
+
+ public static CommandExecutionResult info(String message) {
+ return new CommandExecutionResult(Result.INFO, message);
+ }
+
+ public static CommandExecutionResult warn(String message) {
+ return new CommandExecutionResult(Result.WARN, message);
+ }
+
+ public static CommandExecutionResult error(String message, Exception exception) {
+ return new CommandExecutionResult(Result.ERROR, message, exception);
+ }
+
+ public static CommandExecutionResult error(String message) {
+ return error(message, null);
+ }
+
+ public static CommandExecutionResult none() {
+ return new CommandExecutionResult(Result.NOACTION, null);
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/CommandManager.java b/src/main/java/me/bobert/netneutrality/command/CommandManager.java
new file mode 100644
index 0000000..c2b7e51
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/CommandManager.java
@@ -0,0 +1,91 @@
+package me.bobert.netneutrality.command;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.NetNeutrality;
+import me.bobert.netneutrality.core.LogHandler;
+import net.dv8tion.jda.api.interactions.commands.build.CommandData;
+import net.dv8tion.jda.api.interactions.commands.build.Commands;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.reflections.Reflections;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+public class CommandManager {
+
+ private final static Logger logger = LogManager.getLogger(CommandManager.class);
+ public static final HashMap commands = new HashMap<>();
+
+ public static void init() {
+ initCommands();
+ registerSlashCommands();
+ }
+
+ public static boolean isValidCommand(String commandString) {
+ return commands.containsKey(commandString);
+ }
+
+ public static AbstractCommand getCommand(String commandString) {
+ return commands.get(commandString);
+ }
+
+ private static void initCommands() {
+ logger.info("Loading commands..");
+
+ Reflections reflections = new Reflections("me.bobert.netneutrality.command");
+ Set> classes = reflections.getSubTypesOf(AbstractCommand.class);
+
+ for (Class extends AbstractCommand> s : classes) {
+ try {
+ if (Modifier.isAbstract(s.getModifiers())) {
+ continue;
+ }
+
+ AbstractCommand command = s.getConstructor().newInstance();
+
+ if (!command.isEnabled())
+ continue;
+
+ if (!commands.containsKey(command.getCommandName())) {
+ commands.put(command.getCommandName(), command);
+ }
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+ LogHandler.log(e);
+ }
+ }
+
+ logger.info("Loaded {} commands!", commands.size());
+ }
+
+ private static void registerSlashCommands() {
+ CommandListUpdateAction commandListUpdateAction = NetNeutrality.getJDA().updateCommands();
+ List slashCommands = new ArrayList<>();
+
+ for (AbstractCommand command : commands.values()) {
+ if (!command.isEnabled() || !command.isSlashCommand()) {
+ continue;
+ }
+
+ SlashCommandData data = Commands.slash(command.getCommandName(), command.getDescription());
+ data.setDefaultPermissions(command.getSlashCommandPermission());
+ data.setGuildOnly(command.isGuildOnly());
+ command.registerSlashCommandOptions(data);
+
+ slashCommands.add(data);
+ }
+
+ commandListUpdateAction.addCommands(slashCommands).queue();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/CommandType.java b/src/main/java/me/bobert/netneutrality/command/CommandType.java
new file mode 100644
index 0000000..340993e
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/CommandType.java
@@ -0,0 +1,26 @@
+package me.bobert.netneutrality.command;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+public enum CommandType {
+ MUSIC("Music", 1), ADMINISTRATION("Administration", 0), UTILITY("Utility", 5), CRYPTO("Crypto", 6), UNLISTED("", -1);
+
+ private final String description;
+ private final int id;
+
+ CommandType(String description, int id) {
+ this.description = description;
+ this.id = id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/ICommand.java b/src/main/java/me/bobert/netneutrality/command/ICommand.java
new file mode 100644
index 0000000..6d290b3
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/ICommand.java
@@ -0,0 +1,44 @@
+package me.bobert.netneutrality.command;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public interface ICommand {
+
+ boolean needAdmin();
+
+ boolean isEnabled();
+
+ boolean isGuildOnly();
+
+ boolean isSlashCommand();
+
+ String getCommandName();
+
+ String getDescription();
+
+ DefaultMemberPermissions getSlashCommandPermission();
+
+ void registerSlashCommandOptions(SlashCommandData data);
+
+ CommandType getType();
+
+ boolean canBeExecutedBy(Guild guild, Member member);
+
+ boolean canBeExecutedFrom(Guild guild, MessageChannel channel);
+
+ @Deprecated
+ CommandExecutionResult execute(MessageCommandContext context);
+
+ CommandExecutionResult execute(SlashCommandContext context);
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java
new file mode 100644
index 0000000..9267025
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/admin/BlacklistCommand.java
@@ -0,0 +1,113 @@
+package me.bobert.netneutrality.command.admin;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.db.DBController;
+import me.bobert.netneutrality.db.controllers.DBBlacklist;
+import me.bobert.netneutrality.db.entities.OBlacklist;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
+
+import java.util.ArrayList;
+
+public class BlacklistCommand extends AbstractCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "blacklist";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Manages the blacklist of your server.";
+ }
+
+ @Override
+ public DefaultMemberPermissions getSlashCommandPermission() {
+ return DefaultMemberPermissions.DISABLED;
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addSubcommands(
+ new SubcommandData("get", "Get all blacklisted words for this guild"),
+ new SubcommandData("add", "Add a word to the blacklist")
+ .addOption(OptionType.STRING, "word", "Word to add"),
+ new SubcommandData("remove", "Remove a word from the blacklist")
+ .addOption(OptionType.STRING, "word", "Word to remove")
+ );
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.ADMINISTRATION;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ String sub = context.getEvent().getFullCommandName().split(" ")[1];
+ DBBlacklist controller = DBController.get(DBBlacklist.class);
+ String word = context.getEvent().getOption("word", "", OptionMapping::getAsString);
+ context.ackAndWait(true);
+
+ switch (sub) {
+ case "get" -> {
+
+ context.getHook().editOriginal("Retrieving list, please wait...").queue(t -> {
+ ArrayList blacklists = new ArrayList<>();
+ try {
+ blacklists = controller.getBlacklistForGuild(context.getGuild());
+ } catch (Exception e) {
+ LogHandler.log(e);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (OBlacklist bl : blacklists) {
+ sb.append(bl.getWord()).append(" -> ").append(bl.getEnabled() ? "enabled" : "disabled").append("\n");
+ }
+
+ t.editMessage("List of blacklisted words for this server [" + context.getGuild().getName() + "] :\n" + (blacklists.isEmpty() ? "No word found" : sb.toString())).queue();
+
+ });
+ }
+ case "add" -> controller.add(word, context);
+ case "remove" -> controller.delete(word, context);
+ case "disable" -> controller.disable(word, context);
+ case "enable" -> controller.enable(word, context);
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java
new file mode 100644
index 0000000..840ab45
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/basic/HelpCommand.java
@@ -0,0 +1,76 @@
+package me.bobert.netneutrality.command.basic;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.utils.MessageFunction;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+
+import java.util.List;
+
+public class HelpCommand extends AbstractCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return false;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "help";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Get every command with its usage";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.UTILITY;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ if (context.getArgs().length == 0) {
+ List help = MessageFunction.buildHelpMessage(context.getGuildUser());
+
+ context.reply(new MessageCreateBuilder().addContent(context.getDiscordUser().getAsMention()).addContent(" help has been sent your way!").build());
+ context.replyPrivate(help);
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ List help = MessageFunction.buildHelpMessage(context.getGuildUser());
+
+ context.sendMessageInPrivate(help);
+ return CommandExecutionResult.success("Help has been sent in DM!");
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java
new file mode 100644
index 0000000..e287444
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/basic/PingCommand.java
@@ -0,0 +1,65 @@
+package me.bobert.netneutrality.command.basic;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class PingCommand extends AbstractCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return false;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "ping";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Pong!";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.UTILITY;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ if (context.getArgs().length == 0)
+ context.reply(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing()));
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ return CommandExecutionResult.success(String.format("Pong! [%d ms]", context.getJDA().getGatewayPing()));
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java
new file mode 100644
index 0000000..a74d50d
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/bot/ConfigureCommand.java
@@ -0,0 +1,289 @@
+package me.bobert.netneutrality.command.bot;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.db.GSettings;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.entities.Role;
+import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
+import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+
+import java.util.List;
+import java.util.Map;
+
+public class ConfigureCommand extends AbstractCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "config";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Used to configure the bot on this server";
+ }
+
+ @Override
+ public DefaultMemberPermissions getSlashCommandPermission() {
+ return DefaultMemberPermissions.DISABLED;
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addSubcommands(
+ new SubcommandData("music-volume", "Volume for the music player")
+ .addOptions(new OptionData(OptionType.INTEGER, "volume", "Integer between 0 and 100", true)
+ .setRequiredRange(0, 100)),
+ new SubcommandData("error-logging", "Send errors to #error and/or #log channel")
+ .addOption(OptionType.BOOLEAN, "debug", "Enable or disable the fonctionnality", true),
+ new SubcommandData("music-role", "Role needed to use music commands")
+ .addOption(OptionType.ROLE, "role", "Name of role", true),
+ new SubcommandData("music-channel", "Only allow music commands on this channel")
+ .addOption(OptionType.CHANNEL, "channel", "Leave empty to allow everywhere")
+ );
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.ADMINISTRATION;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ String[] args = context.getArgs();
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+
+ if (args.length > 0) {
+
+ // Role for music commands
+ if (context.getArgument(0).equals("music-role")) {
+ if (args.length == 1) {
+ String roleDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE);
+ Role role = null;
+ if (!roleDb.isEmpty()) {
+ role = context.getGuild().getRoleById(roleDb);
+ }
+
+ context.reply("Current role needed to use music commands (default is everyone): %s", (role == null ? "Everyone" : role.getAsMention()));
+ context.reply("Reset role using 'conf music-role reset' or set it using 'conf musicrole @'");
+ }
+
+ if (args.length == 2) {
+ if (context.getArgument(1).equals("reset")) {
+ if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_ROLE).isEmpty()) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
+ }
+
+ context.reply("Successful reset of the role");
+ }
+
+ List roles = context.getMessage().getMentions().getRoles();
+ if (roles.size() == 1) {
+ Role role = roles.get(0);
+ if (role.getGuild().getId().equals(context.getGuild().getId())) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId());
+ context.reply("Successfully set %s as the role needed for music command", role.getAsMention());
+ }
+ }
+ }
+ }
+
+ // Role for music commands
+ if (context.getArgument(0).equals("music-channel")) {
+ if (args.length == 1) {
+ String channelDb = guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL);
+ TextChannel channel = null;
+ if (!channelDb.isEmpty()) {
+ channel = context.getGuild().getTextChannelById(channelDb);
+ }
+
+ context.reply("Current channel where music commands can be executed (default is everywhere): %s", (channel == null ? "Everywhere" : channel.getAsMention()));
+ context.reply("Reset channel using 'conf music-channel reset' or set it using 'conf music-channel #'");
+ }
+
+ if (args.length == 2) {
+ if (context.getArgument(1).equals("reset")) {
+ if (!guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_CHANNEL).isEmpty()) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null);
+ }
+
+ context.reply("Successful reset of the channel");
+ }
+
+ List channels = context.getMessage().getMentions().getChannels(TextChannel.class);
+ if (channels.size() == 1) {
+ TextChannel channel = channels.get(0);
+ if (channel.getGuild().getId().equals(context.getGuild().getId())) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId());
+ context.reply("Successfully set %s as the channel required to execute music command", channel.getAsMention());
+ }
+ }
+ }
+ }
+
+ // Default volume for music player
+ if (context.getArgument(0).equals("music-volume")) {
+ if (args.length == 1) {
+ int volume = Integer.parseInt(guildManager.getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME));
+
+ context.reply("Default volume for music player: %d", volume);
+ context.reply("Set using 'conf music-volume '");
+ }
+
+ if (args.length == 2) {
+ try {
+ int volume = Integer.parseInt(args[1]);
+
+ if (volume > 0 && volume <= 100) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
+ MusicManager.get(context.getGuild()).setVolume(volume);
+
+ context.reply("Default volume for music player is now %d", volume);
+ } else
+ throw new NumberFormatException();
+
+ } catch (NumberFormatException e) {
+ context.reply("Not a value between 0 and 100");
+ }
+ }
+ }
+
+ // Debug
+ if (context.getArgument(0).equals("debug")) {
+ if (args.length == 1) {
+ boolean debug = guildManager.isDebugEnabled();
+
+ context.reply("Error logging is currently %s for this guild.", (debug ? "enabled" : "disabled"));
+ context.reply("Use 'conf debug 0/1' to toggle between states (0 = disabled, 1 = enabled).");
+ }
+
+ if (args.length == 2) {
+ String str = args[1];
+ boolean debug = guildManager.isDebugEnabled();
+ if (str.equals("0") || str.equalsIgnoreCase("disable")) {
+ debug = false;
+ }
+
+ if (str.equals("1") || str.equalsIgnoreCase("enable")) {
+ debug = true;
+ }
+
+ guildManager.setDebug(debug);
+ context.reply("Error logging is now %s for this guild", (debug ? "enabled" : "disabled"));
+
+ }
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ String sub = context.getEvent().getFullCommandName().split(" ")[1];
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+ context.ackAndWait(true);
+
+ if ("music-role".equals(sub)) {
+ Role role = context.getEvent().getOption("role", OptionMapping::getAsRole);
+ assert role != null;
+
+ if (role.getPosition() == -1) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, null);
+
+ return CommandExecutionResult.success("Everyone can now use music commands");
+ }
+
+ if (role.getGuild().getId().equals(context.getGuild().getId())) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_ROLE, role.getId());
+
+ return CommandExecutionResult.success(String.format("Successfully set %s as the role needed for music command", role.getAsMention()));
+ }
+ }
+
+ if ("music-channel".equals(sub)) {
+ GuildChannelUnion channel = context.getEvent().getOption("channel", OptionMapping::getAsChannel);
+
+ if (channel == null) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, null);
+
+ return CommandExecutionResult.success("No channel restrictions for music commands");
+ }
+
+ if (channel instanceof TextChannel) {
+ if (channel.getGuild().getId().equals(context.getGuild().getId())) {
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_CHANNEL, channel.getId());
+
+ return CommandExecutionResult.success(String.format("Successfully set %s as the channel required to execute music command", channel.getAsMention()));
+ }
+ }
+
+ }
+
+ if ("music-volume".equals(sub)) {
+ int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt);
+ guildManager.getGuildConfigManager().setSettingValue(GSettings.MUSIC_VOLUME, String.valueOf(volume));
+ MusicManager.get(context.getGuild()).setVolume(volume);
+
+ return CommandExecutionResult.success(String.format("Default volume for music player is now %d", volume));
+ }
+
+ if ("debug".equals(sub)) {
+ boolean debug = context.getEvent().getOption("debug", true, OptionMapping::getAsBoolean);
+ guildManager.setDebug(debug);
+
+ return CommandExecutionResult.success(String.format("Error logging is now %s for this guild", (debug ? "enabled" : "disabled")));
+ }
+
+ if ("show-all".equals(sub)) {
+ Map settings = guildManager.getGuildConfigManager().getSettings();
+ MessageCreateBuilder builder = new MessageCreateBuilder();
+
+ for (GSettings o : GSettings.values()) {
+ String value = o.getDefaultValue();
+
+ if (settings.containsKey(o.getName())) {
+ value = settings.get(o.getName());
+ }
+
+ builder.addContent(o.getName()).addContent(": ").addContent(value);
+ builder.addContent("\n");
+ }
+
+ context.replyEphemeral(builder.build());
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java
new file mode 100644
index 0000000..597492c
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/crypto/CryptoPriceCommand.java
@@ -0,0 +1,151 @@
+package me.bobert.netneutrality.command.crypto;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.http.WebModule;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.NumberFormat;
+import java.util.Currency;
+import java.util.Locale;
+
+public class CryptoPriceCommand extends AbstractCommand {
+
+ private final String baseURL = "https://min-api.cryptocompare.com/data/price";
+
+ @Override
+ public boolean needAdmin() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isGuildOnly() {
+ return false;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "crypto";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Get the price of cryptocurrencies";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addOption(OptionType.STRING, "symbol", "The crypto symbol to fetch price for", true);
+ data.addOption(OptionType.STRING, "currency", "In which currency should the price be displayed");
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.CRYPTO;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ String[] args = context.getArgs();
+ String currency = "USD";
+
+ if (args.length > 0) {
+ if (args.length == 2) {
+ try {
+ currency = Currency.getInstance(args[1].toUpperCase()).getCurrencyCode();
+ } catch (IllegalArgumentException exception) {
+
+ return CommandExecutionResult.warn("Please enter a valid currency");
+ }
+ }
+
+ String crypto = args[0].toUpperCase();
+ String finalCurrency = currency;
+ context.getChannel().sendMessage("Retreiving price, please wait...").queue(t -> {
+ String price;
+ Locale usa = new Locale("en", "US");
+ NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa);
+ dollarFormat.setMaximumFractionDigits(24);
+
+ try {
+ WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, crypto, finalCurrency.toUpperCase())).request();
+ JSONObject json = http.getAsObject();
+ if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) {
+ t.editMessage("Crypto doesn't exist.. yet").queue();
+ return;
+ }
+
+ price = dollarFormat.format(json.getDouble(finalCurrency));
+ t.editMessage(String.format("The current price of %s in %s is %s", crypto, finalCurrency, price)).queue();
+
+ } catch (JSONException | IllegalArgumentException | ArithmeticException e) {
+ t.editMessage(String.format("Error getting price of %s!", crypto)).queue();
+ LogHandler.log(e);
+ }
+ });
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ Locale usa = new Locale("en", "US");
+ NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(usa);
+ dollarFormat.setMaximumFractionDigits(24);
+
+ String symbol = context.getEvent().getOption("symbol", OptionMapping::getAsString);
+ String currency = context.getEvent().getOption("currency", "USD", OptionMapping::getAsString);
+
+ try {
+ currency = Currency.getInstance(currency).getCurrencyCode();
+ } catch (IllegalArgumentException exception) {
+ return CommandExecutionResult.error("Please enter a valid currency");
+ }
+
+ String finalCurrency = currency.toUpperCase();
+ context.getEvent().deferReply().queue(interactionHook -> {
+ try {
+
+ WebModule http = new WebModule(String.format("%s?fsym=%s&tsyms=%s", baseURL, symbol, finalCurrency)).request();
+ JSONObject json = http.getAsObject();
+
+ if (json.isEmpty() || (json.has("Response") && json.getString("Response").equalsIgnoreCase("Error"))) {
+ interactionHook.editOriginal("Crypto doesn't exist.. yet").queue();
+ return;
+ }
+
+ double value = json.getDouble(finalCurrency);
+ String price = dollarFormat.format(value);
+
+ interactionHook.editOriginal(String.format("The current price of %s in %s is %s", symbol, finalCurrency, price)).queue();
+
+ } catch (JSONException | IllegalArgumentException | ArithmeticException e) {
+ interactionHook.editOriginal(String.format("Error getting price of %s!", symbol)).queue();
+ LogHandler.log(e);
+ }
+
+
+ });
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/custom/.gitignore b/src/main/java/me/bobert/netneutrality/command/custom/.gitignore
new file mode 100644
index 0000000..2a9bbbd
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/custom/.gitignore
@@ -0,0 +1 @@
+*.java
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java
new file mode 100644
index 0000000..5e5947b
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/BitconnectCommand.java
@@ -0,0 +1,61 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class BitconnectCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "bitconnect";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Ready to invest?";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandType getType() {
+ return CommandType.UNLISTED;
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+ player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true);
+
+ context.deleteMessage();
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ player.play("https://www.youtube.com/watch?v=e5nyQmaq4k4", context, false, true);
+ return CommandExecutionResult.success("Ready to invest?");
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java
new file mode 100644
index 0000000..30f06a6
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/ClearQueueCommand.java
@@ -0,0 +1,99 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MessageFunction;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class ClearQueueCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean needAdmin() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "clearqueue";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Empties the music queue";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (!MusicUtils.checkIfConnected(player, context))
+ return CommandExecutionResult.none();
+
+ if (context.getArgs().length == 0) {
+ context.reply("Select the track you want to delete from the queue (track# or all)");
+ context.reply(MessageFunction.buildViewQueueMessage(player, true));
+ }
+
+ if (context.getArgs().length > 0) {
+
+ int queueSize = player.getQueueSize();
+ if (context.getArgument(0).equals("all")) {
+
+ if (queueSize > 0) {
+ player.getScheduler().getQueueManager().clearQueue();
+ context.reply(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
+ } else {
+ context.reply("Queue is empty!");
+ }
+ } else {
+
+ try {
+ int num = Integer.parseInt(context.getArgument(0));
+
+ if (num > 0) {
+ return player.getScheduler().getQueueManager().removeFromQueue(num);
+ } else {
+ context.reply("Number out of range");
+ }
+
+ } catch (NumberFormatException e) {
+ context.reply("Please enter a number");
+ }
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (!MusicUtils.checkIfConnected(manager, context))
+ return CommandExecutionResult.none();
+
+ return manager.getScheduler().getQueueManager().clearQueue();
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java
new file mode 100644
index 0000000..9c432c0
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/CurrentlyPlayingCommand.java
@@ -0,0 +1,64 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MessageFunction;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class CurrentlyPlayingCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "nowplaying";
+ }
+
+ @Override
+ public String getDescription() {
+ return "View currently playing track";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ // no arguments
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (MusicUtils.checkIfConnected(player, context) && player.isCurrentlyPlaying()) {
+ context.reply(MessageFunction.buildCurrentlyPlayingMessage(player));
+ } else {
+ context.reply("Bot isn't playing any tracks right now");
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(manager, context) && manager.isCurrentlyPlaying()) {
+ context.reply(MessageFunction.buildCurrentlyPlayingMessage(manager));
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java
new file mode 100644
index 0000000..85c4d78
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/JoinCommand.java
@@ -0,0 +1,90 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.exceptions.CommandExecutionException;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class JoinCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "join";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Make the bot join your current voice channel";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (context.getGuildUser().getVoiceState() == null) {
+ throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
+ }
+
+ if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
+ return CommandExecutionResult.warn("You are not in a voice channel");
+ }
+
+ if (player.isConnected()) {
+ return CommandExecutionResult.warn("Player is already in a voice channel");
+ }
+
+ VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
+ if (vChannel != null) {
+ player.connect(vChannel);
+ context.reply("Joining current voice channel");
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (context.getGuildUser().getVoiceState() == null) {
+ throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
+ }
+
+ if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
+ return CommandExecutionResult.warn("You are not in a voice channel");
+ }
+
+ if (player.isConnected()) {
+ return CommandExecutionResult.warn("Player is already in a voice channel");
+ }
+
+ VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
+ if (vChannel != null) {
+ player.connect(vChannel);
+ context.reply("Joining current voice channel");
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java
new file mode 100644
index 0000000..806d5a7
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/LeaveCommand.java
@@ -0,0 +1,63 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class LeaveCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "leave";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Make the bot leave the voice channel";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (MusicUtils.checkIfConnected(player, context)) {
+ player.leave();
+ context.reply("Leaving voice channel and clearing queue");
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(player, context)) {
+ player.leave();
+ context.reply("Leaving voice channel and clearing queue");
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java
new file mode 100644
index 0000000..5f3da37
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/PlayCommand.java
@@ -0,0 +1,60 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class PlayCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "play";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Get the party going!";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addOption(OptionType.STRING, "query", "URL or search query", true);
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (context.getArgs().length > 0) {
+ player.play(context.getMessageText(), context, false, false);
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ String query = context.getEvent().getOption("query", OptionMapping::getAsString);
+ context.ackAndWait();
+
+ manager.play(query, context, false, false);
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java
new file mode 100644
index 0000000..1d7075f
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/QueueCommand.java
@@ -0,0 +1,163 @@
+package me.bobert.netneutrality.command.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MessageFunction;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
+
+public class QueueCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "queue";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Manage the music queue";
+ }
+
+ @Override
+ public DefaultMemberPermissions getSlashCommandPermission() {
+ return DefaultMemberPermissions.DISABLED;
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addSubcommands(
+ new SubcommandData("clear-all", "Clear all queued tracks"),
+ new SubcommandData("delete-track", "Remove a single track from the queue")
+ .addOptions(new OptionData(OptionType.INTEGER, "position", "Position of track", true)
+ .setMinValue(1)),
+ new SubcommandData("show-queue", "Display current queue"),
+ new SubcommandData("shuffle", "Shuffle the queue"),
+ new SubcommandData("from-user", "View tracks added by a user")
+ .addOption(OptionType.USER, "user", "Which user?", true),
+ new SubcommandData("remove-from-user", "Remove all tracks added by a user")
+ .addOption(OptionType.USER, "user", "Which user?", true)
+ );
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (!MusicUtils.checkIfConnected(player, context))
+ return CommandExecutionResult.none();
+
+ if (context.getArgs().length == 0) {
+ context.reply("Select the track you want to delete from the queue (track# or all)");
+ context.reply(MessageFunction.buildViewQueueMessage(player, true));
+ }
+
+ if (context.getArgs().length > 0) {
+
+ int queueSize = player.getQueueSize();
+ if (context.getArgument(0).equals("all")) {
+
+ if (queueSize > 0) {
+ player.getScheduler().getQueueManager().clearQueue();
+ context.reply(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
+ } else {
+ context.reply("Queue is empty!");
+ }
+ } else {
+
+ try {
+ int num = Integer.parseInt(context.getArgument(0));
+
+ if (num > 0) {
+ return player.getScheduler().getQueueManager().removeFromQueue(num);
+ } else {
+ context.reply("Number out of range");
+ }
+
+ } catch (NumberFormatException e) {
+ context.reply("Please enter a number");
+ }
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+ String sub = context.getEvent().getFullCommandName().split(" ")[1];
+
+ if (!MusicUtils.checkIfConnected(manager, context)) {
+ return CommandExecutionResult.none();
+ }
+
+ switch (sub) {
+ case "clear-all" -> {
+ return manager.getScheduler().getQueueManager().clearQueue();
+ }
+
+ case "delete-track" -> {
+ int position = context.getEvent().getOption("position", 0, OptionMapping::getAsInt);
+ return manager.getScheduler().getQueueManager().removeFromQueue(position);
+ }
+
+ case "show-queue" -> context.reply(MessageFunction.buildViewQueueMessage(manager, true));
+
+ case "shuffle" -> {
+ return manager.getScheduler().getQueueManager().shuffle();
+ }
+
+ case "from-user" -> {
+ Member user = context.getEvent().getOption("user", OptionMapping::getAsMember);
+ if (user == null) {
+ context.reply("The user isn't from this guild");
+ break;
+ }
+
+ context.reply(MessageFunction.buildUserTracksMessage(manager, user));
+ }
+
+ case "remove-from-user" -> {
+ Member member = context.getEvent().getOption("user", OptionMapping::getAsMember);
+ if (member == null) {
+ context.reply("The user isn't from this guild");
+ break;
+ }
+
+ int size = manager.getScheduler()
+ .getQueueManager()
+ .getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester()))
+ .size();
+
+ if (size == 0) {
+ context.reply("No tracks added by user %s", member.getEffectiveName());
+ } else {
+ manager.getScheduler().getQueueManager().removeTracksFromUser(member);
+ context.reply("Removed all tracks from user %s", member.getEffectiveName());
+ }
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java
new file mode 100644
index 0000000..e4ea2c2
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/ShuffleCommand.java
@@ -0,0 +1,48 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class ShuffleCommand extends AbstractMusicCommand {
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "shuffle";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Shuffle the music queue";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ return manager.getScheduler().getQueueManager().shuffle();
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java
new file mode 100644
index 0000000..7db97f4
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/SkipCommand.java
@@ -0,0 +1,84 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import me.bobert.netneutrality.utils.UserFunction;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class SkipCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "skip";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Skip to the next track in the queue";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addOptions(new OptionData(OptionType.INTEGER, "skip", "Number of tracks to skip")
+ .setMinValue(1));
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (!MusicUtils.checkIfConnected(player, context))
+ return CommandExecutionResult.none();
+
+ if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) {
+ if (UserFunction.isAdmin(context.getGuildUser()) && context.getArgs().length > 0) {
+ try {
+ int skips = Integer.parseInt(context.getArgument(0));
+ player.getScheduler().getQueueManager().skip(skips);
+
+ context.reply("Skipping %s track%s", skips, (skips > 1 ? "s" : ""));
+ } catch (NumberFormatException e) {
+ context.reply("Please enter how many track(s) to skip");
+ }
+ } else {
+ player.getScheduler().getQueueManager().skip(1);
+ context.reply("Skipping current track");
+ }
+ } else {
+ player.voteSkip(context);
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ int skips = context.getEvent().getOption("skip", 1, OptionMapping::getAsInt);
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ return manager.getScheduler().getQueueManager().skip(skips);
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java
new file mode 100644
index 0000000..48e772d
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/StopCommand.java
@@ -0,0 +1,68 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import me.bobert.netneutrality.utils.UserFunction;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class StopCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "stop";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Stop the music player, keeping the current queue.";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (!MusicUtils.checkIfConnected(player, context))
+ return CommandExecutionResult.none();
+
+ // Need a resume command before
+ if (UserFunction.isAdmin(context.getGuildUser())) {
+ player.stopTrack();
+ context.reply("Stopping current track");
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ manager.stopTrack();
+ return CommandExecutionResult.success("Stopping music playback");
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java
new file mode 100644
index 0000000..84808dc
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/ViewQueueCommand.java
@@ -0,0 +1,72 @@
+package me.bobert.netneutrality.command.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MessageFunction;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class ViewQueueCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "viewqueue";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Get the current queue for the music player";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (MusicUtils.checkIfConnected(player, context)) {
+ int queueSize = player.getQueueSize();
+ if (queueSize > 0) {
+ context.reply(MessageFunction.buildViewQueueMessage(player, true));
+ } else {
+ context.reply("Queue is empty!");
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ context.ackAndWait();
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ int queueSize = manager.getQueueSize();
+
+ if (queueSize > 0) {
+ context.reply(MessageFunction.buildViewQueueMessage(manager, true));
+ } else {
+ return CommandExecutionResult.warn("Queue is empty!");
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java
new file mode 100644
index 0000000..1a7178c
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/VolumeCommand.java
@@ -0,0 +1,81 @@
+package me.bobert.netneutrality.command.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import me.bobert.netneutrality.utils.UserFunction;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class VolumeCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "volume";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets the volume for the music player";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+ data.addOptions(new OptionData(OptionType.INTEGER, "volume", "Playback volume for this session", true)
+ .setRequiredRange(0, 100));
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager player = MusicManager.get(context.getGuild());
+
+ if (!MusicUtils.checkIfConnected(player, context))
+ return CommandExecutionResult.none();
+
+ if (UserFunction.isAdmin(context.getGuildUser()) || MusicUtils.isUserDj(context.getGuildUser())) {
+ try {
+ int volume = Integer.parseInt(context.getArgument(0));
+
+ if (volume > 0 && volume <= 100) {
+ player.setVolume(volume);
+ context.reply("Volume set to %s", volume);
+ } else
+ throw new NumberFormatException();
+
+ } catch (NumberFormatException e) {
+ context.reply("Please enter a value from 0 to 100");
+ }
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+ int volume = context.getEvent().getOption("volume", 30, OptionMapping::getAsInt);
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ manager.setVolume(volume);
+ return CommandExecutionResult.success(String.format("Volume set to %s", volume));
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java
new file mode 100644
index 0000000..ce84c07
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/command/music/VoteSkipCommand.java
@@ -0,0 +1,59 @@
+package me.bobert.netneutrality.command.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractMusicCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.utils.MusicUtils;
+import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
+
+public class VoteSkipCommand extends AbstractMusicCommand {
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public String getCommandName() {
+ return "voteskip";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Cast a vote to skip the currently playing track";
+ }
+
+ @Override
+ public void registerSlashCommandOptions(SlashCommandData data) {
+
+ }
+
+ @Override
+ public CommandExecutionResult execute(MessageCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ manager.voteSkip(context);
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ @Override
+ public CommandExecutionResult execute(SlashCommandContext context) {
+ MusicManager manager = MusicManager.get(context.getGuild());
+
+ if (MusicUtils.checkIfConnected(manager, context)) {
+ return manager.voteSkip(context);
+ }
+
+ return CommandExecutionResult.none();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/BotState.java b/src/main/java/me/bobert/netneutrality/core/BotState.java
new file mode 100644
index 0000000..db78b80
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/BotState.java
@@ -0,0 +1,71 @@
+package me.bobert.netneutrality.core;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.music.TrackScheduler;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+
+public class BotState {
+ public static boolean SAFE = false;
+
+ //TODO: is it needed?
+ private static void saveGuildsState() {
+ Map managers = GuildManager.getManagers();
+
+ JSONObject main = new JSONObject();
+ JSONArray guildsArray = new JSONArray();
+
+ for (GuildManager manager : managers.values()) {
+ MusicManager musicManager = MusicManager.get(manager.getGuild());
+ TrackScheduler trackScheduler = musicManager.getScheduler();
+
+ JSONObject guild = new JSONObject();
+ JSONObject music = new JSONObject();
+ guild.put("id", manager.getGuild().getId());
+
+ MessageChannel currentChannel = trackScheduler.getTextChannel();
+ if (currentChannel == null) {
+ music.put("current_channel", "null");
+ } else {
+ music.put("current_channel", currentChannel.getId());
+ }
+
+ guild.put("music", music);
+
+ guildsArray.put(guild);
+ }
+
+ main.put("guilds", guildsArray);
+
+ File state = new File("state.json");
+ try {
+ if (!state.exists()) {
+ state.createNewFile();
+ }
+
+ try (FileWriter writer = new FileWriter(state)) {
+ writer.write(main.toString());
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void dump() {
+ if (!SAFE) {
+ saveGuildsState();
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/CommandProcessor.java b/src/main/java/me/bobert/netneutrality/core/CommandProcessor.java
new file mode 100644
index 0000000..e76021a
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/CommandProcessor.java
@@ -0,0 +1,112 @@
+package me.bobert.netneutrality.core;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Sentry;
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.command.CommandManager;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.exceptions.CommandExecutionException;
+import me.bobert.netneutrality.utils.UserFunction;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static me.bobert.netneutrality.command.CommandExecutionResult.Result.ERROR;
+import static me.bobert.netneutrality.command.CommandExecutionResult.Result.NOACTION;
+
+public class CommandProcessor {
+
+ private final static Logger logger = LogManager.getLogger(CommandProcessor.class);
+ private final String commandName;
+ private final CommandContext context;
+
+ public CommandProcessor(String commandName, CommandContext context) {
+ this.commandName = commandName;
+ this.context = context;
+ }
+
+ public void dispatch() {
+
+ if (!CommandManager.isValidCommand(commandName)) {
+ return;
+ }
+
+ AbstractCommand command = CommandManager.getCommand(commandName);
+ logger.info("Executing command {} on guild {}", command.getCommandName(), context.getGuild().getId());
+
+ if (context instanceof SlashCommandContext slashCommandContext)
+ logger.debug("SlashCommandInteractionId: {}", slashCommandContext.getEvent().getInteraction().getId());
+
+ configureSentry(command);
+
+ try {
+
+ if (context instanceof MessageCommandContext && command.needAdmin() && !UserFunction.isAdmin(context.getGuildUser())) {
+ context.reply("You are not powerful enough, scrub");
+ return;
+ }
+
+ if (command.canBeExecutedBy(context.getGuild(), context.getGuildUser())) {
+ if (command.canBeExecutedFrom(context.getGuild(), context.getChannel())) {
+ CommandExecutionResult commandExecutionResult = null;
+
+ if (context instanceof SlashCommandContext slashCommandContext) {
+ commandExecutionResult = command.execute(slashCommandContext);
+ }
+
+ if (context instanceof MessageCommandContext messageCommandContext) {
+ commandExecutionResult = command.execute(messageCommandContext);
+ }
+
+ if (commandExecutionResult == null) {
+ throw new RuntimeException("Command type is not implemented.");
+ }
+
+ CommandExecutionResult.Result result = commandExecutionResult.getResult();
+ String message = commandExecutionResult.getMessage();
+ if (result == NOACTION) {
+ return;
+ }
+
+ if (result == ERROR) {
+ Exception exception = commandExecutionResult.getException();
+
+ logger.error("Error from command {}: {}", command.getCommandName(), message, exception);
+ }
+
+ context.reply(message);
+ }
+ }
+
+ //TODO: make better
+ } catch (CommandExecutionException exception) {
+ LogHandler.log(exception);
+ } catch (Exception exception) {
+ LogHandler.log(new CommandExecutionException("Error during command execution", context, exception));
+ }
+ }
+
+ private void configureSentry(AbstractCommand command) {
+
+ Sentry.configureScope(scope -> {
+ scope.setContexts("command", command.getCommandName());
+ scope.setContexts("isAdmin", UserFunction.isAdmin(context.getGuildUser()));
+ scope.setContexts("canBeExecuted", command.canBeExecutedBy(context.getGuild(), context.getGuildUser()));
+ scope.setTag("channel", context.getChannel().getId());
+
+ if (context instanceof SlashCommandContext slashCommandContext)
+ scope.setTag("interactionId", slashCommandContext.getEvent().getInteraction().getId());
+
+ if (context instanceof MessageCommandContext messageCommandContext)
+ scope.setTag("message", messageCommandContext.getMessage().getId());
+
+ scope.setTag("guild", context.getGuild().getId());
+ });
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java b/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java
new file mode 100644
index 0000000..6e30312
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/GuildConfigManager.java
@@ -0,0 +1,97 @@
+package me.bobert.netneutrality.core;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.db.GSettings;
+import me.bobert.netneutrality.db.entities.OGuild;
+import me.bobert.netneutrality.db.entities.OGuildSetting;
+import me.bobert.netneutrality.utils.JpaUtils;
+import net.dv8tion.jda.api.entities.Guild;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Session;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GuildConfigManager {
+
+ private static final Logger logger = LogManager.getLogger(GuildConfigManager.class);
+ private final Guild guild;
+ private final OGuild guildEntity;
+
+ private final Map settingsCache = new HashMap<>();
+
+ public GuildConfigManager(Guild guild, OGuild guildEntity) {
+ this.guild = guild;
+ this.guildEntity = guildEntity;
+
+ reloadSettingsCache();
+ }
+
+ public Map getSettings() {
+ return settingsCache;
+ }
+
+ public String getSetting(GSettings setting) {
+ String value = settingsCache.get(setting.getName());
+
+ if (value == null) {
+ value = setting.getDefaultValue();
+ }
+
+ return value;
+ }
+
+
+ public void setSettingValue(GSettings setting, String value) {
+ Session session = SessionManager.getSessionFactory().openSession();
+ JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
+
+ OGuildSetting oGuildSetting = guildEntity.getSettingByName(setting.getName());
+
+ session.beginTransaction();
+ if (value == null || setting.getDefaultValue().equals(value)) {
+ if (oGuildSetting != null) {
+ session.remove(oGuildSetting);
+ }
+ } else {
+ if (oGuildSetting == null) {
+ oGuildSetting = new OGuildSetting();
+ oGuildSetting.setGuild(guildEntity);
+ oGuildSetting.setName(setting.getName());
+ oGuildSetting.setValue(value);
+
+ session.persist(oGuildSetting);
+ } else {
+ oGuildSetting.setValue(value);
+
+ session.merge(oGuildSetting);
+ }
+ }
+
+ session.getTransaction().commit();
+ session.close();
+
+ reloadSettingsCache();
+ }
+
+ public void reloadSettingsCache() {
+ logger.debug("Reloading settings cache for guild: {}", guild.getId());
+ settingsCache.clear();
+
+ JpaUtils.getWithSession(guildEntity, session -> {
+ Set settingSet = guildEntity.getGuildSettings();
+
+ for (OGuildSetting setting : settingSet) {
+ settingsCache.put(setting.getName(), setting.getValue());
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/GuildManager.java b/src/main/java/me/bobert/netneutrality/core/GuildManager.java
new file mode 100644
index 0000000..8ed2587
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/GuildManager.java
@@ -0,0 +1,168 @@
+package me.bobert.netneutrality.core;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.NetNeutrality;
+import me.bobert.netneutrality.db.DBController;
+import me.bobert.netneutrality.db.controllers.DBMessage;
+import me.bobert.netneutrality.db.entities.OBlacklist;
+import me.bobert.netneutrality.db.entities.OGuild;
+import me.bobert.netneutrality.thread.ThreadManager;
+import me.bobert.netneutrality.utils.JpaUtils;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class GuildManager {
+
+ private static final Logger logger = LogManager.getLogger(GuildManager.class);
+ private static final Map managers = new ConcurrentHashMap<>();
+ private static final SessionFactory sessionFactory = SessionManager.getSessionFactory();
+ private final Set blacklistCache = new HashSet<>();
+ private final GuildConfigManager guildConfigManager;
+ private final Guild guild;
+ private OGuild guildEntity;
+
+ private GuildManager(Guild guild) {
+ this.guild = guild;
+ this.guildEntity = loadGuildFromDb(guild.getId());
+ this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity);
+
+ managers.put(guild.getId(), this);
+ reloadBlacklistCache();
+ }
+
+ private GuildManager(String guildId) {
+ Guild temp = NetNeutrality.getJDA().getGuildById(guildId);
+ if (temp == null)
+ throw new IllegalArgumentException("Guild does not exist in current cache");
+
+ this.guild = temp;
+ this.guildEntity = loadGuildFromDb(guild.getId());
+ this.guildConfigManager = new GuildConfigManager(guild, this.guildEntity);
+
+ managers.put(guild.getId(), this);
+ reloadBlacklistCache();
+ }
+
+ public static GuildManager getManager(Guild guild) {
+ if (managers.containsKey(guild.getId())) {
+ return managers.get(guild.getId());
+ } else {
+ return new GuildManager(guild);
+ }
+ }
+
+ public static GuildManager getManager(String guildId) {
+ if (managers.containsKey(guildId)) {
+ return managers.get(guildId);
+ } else {
+ return new GuildManager(guildId);
+ }
+ }
+
+ // Getter
+ public static Map getManagers() {
+ return managers;
+ }
+
+ public Guild getGuild() {
+ return guild;
+ }
+
+ public OGuild getGuildEntity() {
+ return guildEntity;
+ }
+
+ public Set getBlacklistCache() {
+ return blacklistCache;
+ }
+
+ public GuildConfigManager getGuildConfigManager() {
+ return guildConfigManager;
+ }
+
+ // Methods
+ public boolean isLoggingEnabled() {
+ return guildEntity.getLogging();
+ }
+
+ public boolean isDebugEnabled() {
+ return guildEntity.getDebug();
+ }
+
+ public void setDebug(boolean toggle) {
+ if (toggle == isDebugEnabled())
+ return;
+
+ JpaUtils.updateWithTransaction(guildEntity, () -> guildEntity.setDebug(toggle));
+ }
+
+ public void reloadBlacklistCache() {
+ logger.debug("Reloading blacklist cache for guild: {}", guild.getId());
+ blacklistCache.clear();
+
+ JpaUtils.getWithSession(guildEntity, session -> {
+ Set oBlacklistSet = guildEntity.getBlacklists();
+
+ for (OBlacklist bl : oBlacklistSet) {
+ if (bl.getEnabled()) {
+ blacklistCache.add(bl.getWord());
+ }
+ }
+ });
+ }
+
+ private OGuild loadGuildFromDb(String guildId) {
+ Session session = sessionFactory.openSession();
+ OGuild entity = session.get(OGuild.class, guildId);
+
+ if (entity == null) {
+ logger.debug("Guild[{}] doesn't exist in DB, creating entity..", guildId);
+ session.beginTransaction();
+ entity = new OGuild();
+
+ entity.setGuildId(guildId);
+
+ session.persist(entity);
+ session.getTransaction().commit();
+
+ guild.getTextChannels()
+ .stream()
+ .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
+ .forEach(textChannel -> textChannel.sendMessage("Error logging enabled on channel " + textChannel.getAsMention()).queue());
+ }
+
+ session.close();
+ return entity;
+ }
+
+ public void reloadEntity() {
+ this.guildEntity = loadGuildFromDb(guild.getId());
+ }
+
+ public void removeFromDB() {
+ Session session = sessionFactory.openSession();
+ session.beginTransaction();
+
+ session.remove(guildEntity);
+ session.getTransaction().commit();
+ session.close();
+
+ managers.remove(guild.getId());
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/LogHandler.java b/src/main/java/me/bobert/netneutrality/core/LogHandler.java
new file mode 100644
index 0000000..9e9a09b
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/LogHandler.java
@@ -0,0 +1,85 @@
+package me.bobert.netneutrality.core;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Sentry;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import me.bobert.netneutrality.exceptions.CommandException;
+import me.bobert.netneutrality.exceptions.CommandExecutionException;
+import me.bobert.netneutrality.utils.MessageFunction;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class LogHandler {
+
+ private final static Logger logger = LogManager.getLogger(LogHandler.class);
+
+ /**
+ * Log an exception accordingly
+ *
+ * @param e The exceptions to log
+ */
+ public static void log(Exception e) {
+ Sentry.captureException(e);
+
+ if (e instanceof CommandExecutionException exception) {
+ CommandContext context = exception.getCommandContext();
+
+ logInternally(exception, context);
+ tryLogToDebugChannel(exception, context);
+ tryGracefulSlashContextError(exception, context);
+
+ return;
+ }
+
+ logger.error(e.getMessage(), e);
+ }
+
+ public static void log(String s, Exception e) {
+ logger.error(s, e);
+ }
+
+ public static void logThread(Thread t, Throwable e) {
+ logger.error("Uncaught exception thrown in thread {}", t);
+ log((Exception) e);
+ }
+
+ public static void sendMessageToDebugChannel(Guild guild, String message) {
+ if (GuildManager.getManager(guild).isDebugEnabled()) {
+ guild.getTextChannels()
+ .stream()
+ .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
+ .filter(TextChannel::canTalk)
+ .forEach(textChannel -> textChannel.sendMessage(message).queue());
+ }
+ }
+
+ private static void logInternally(CommandException exception, CommandContext context) {
+ logger.error("Exception thrown from command [{}] in guild [{}]: {}", context.getCommand().getCommandName(), context.getGuild().getId(), exception.getMessage(), exception);
+ }
+
+ private static void tryLogToDebugChannel(CommandException exception, CommandContext context) {
+ if (context.getGuild() != null) {
+ if (GuildManager.getManager(context.getGuild()).isDebugEnabled()) {
+ context.getGuild()
+ .getTextChannels()
+ .stream()
+ .filter(textChannel -> textChannel.getName().equals("debug") || textChannel.getName().equals("log"))
+ .filter(TextChannel::canTalk)
+ .forEach(textChannel -> textChannel.sendMessageEmbeds(MessageFunction.buildDebugMessage(context, exception)).queue());
+ }
+ }
+ }
+
+ private static void tryGracefulSlashContextError(CommandException exception, CommandContext context) {
+ if (context instanceof SlashCommandContext slashCommandContext) {
+ slashCommandContext.reply(MessageFunction.buildSentryErrorMessage(exception));
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/SessionManager.java b/src/main/java/me/bobert/netneutrality/core/SessionManager.java
new file mode 100644
index 0000000..fab89e5
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/SessionManager.java
@@ -0,0 +1,68 @@
+package me.bobert.netneutrality.core;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Sentry;
+import me.bobert.netneutrality.utils.Settings;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.SessionFactory;
+import org.hibernate.boot.Metadata;
+import org.hibernate.boot.MetadataSources;
+import org.hibernate.boot.registry.StandardServiceRegistry;
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.cfg.Environment;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SessionManager {
+ private final static Logger logger = LogManager.getLogger(SessionManager.class);
+ private static StandardServiceRegistry registry;
+ private static SessionFactory sessionFactory;
+
+ public static SessionFactory getSessionFactory() {
+ if (sessionFactory == null) {
+ try {
+ StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder().configure();
+ Map settings = new HashMap<>();
+ String dbName = Settings.getInstance().get("MYSQL_DATABASE", "netneutrality");
+ String dbUser = Settings.getInstance().get("MYSQL_USER", "root");
+ String dbPass = Settings.getInstance().get("MYSQL_PASSWORD", "");
+ String dbHost = Settings.getInstance().get("MYSQL_HOST", "localhost");
+ logger.info(String.format("Connecting to database '%s' at %s:3306 with user '%s'", dbName, dbHost, dbUser));
+
+ settings.put(Environment.URL, String.format("jdbc:mysql://%s:3306/%s", dbHost, dbName));
+ settings.put(Environment.USER, dbUser);
+ settings.put(Environment.PASS, dbPass);
+ registryBuilder.applySettings(settings);
+
+ registry = registryBuilder.build();
+ MetadataSources sources = new MetadataSources(registry);
+ Metadata metadata = sources.getMetadataBuilder().build();
+ sessionFactory = metadata.getSessionFactoryBuilder().build();
+
+ } catch (Exception e) {
+ String env = Settings.getInstance().get("NETNEUTRALITY_BOT_ENV", "dev");
+ if (!"dev".equals(env)) {
+ Sentry.captureException(e);
+ }
+
+ shutdown();
+ logger.error("Error connecting to database. Shutting down..");
+ System.exit(1);
+ }
+ }
+
+ return sessionFactory;
+ }
+
+ public static void shutdown() {
+ if (registry != null) {
+ StandardServiceRegistryBuilder.destroy(registry);
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java
new file mode 100644
index 0000000..74d7abb
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/context/CommandContext.java
@@ -0,0 +1,60 @@
+package me.bobert.netneutrality.core.context;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.Event;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+public interface CommandContext {
+
+ AbstractCommand getCommand();
+
+ Event getEvent();
+
+ Guild getGuild();
+
+ MessageChannel getChannel();
+
+ Member getGuildUser();
+
+ User getDiscordUser();
+
+ JDA getJDA();
+
+ void deleteMessage();
+
+ void reply(MessageCreateData message);
+
+ void reply(String message);
+
+ void reply(MessageEmbed... embeds);
+
+ default void reply(String format, Object... arguments) {
+ reply(String.format(format, arguments));
+ }
+
+ void replyToMessage(String message);
+
+ default void replyToMessage(MessageCreateData message) {
+ reply(message);
+ }
+
+ default void replyToMessage(String format, Object... arguments) {
+ replyToMessage(String.format(format, arguments));
+ }
+
+ default void replyToMessage(MessageEmbed... embeds) {
+ replyToMessage(MessageCreateData.fromEmbeds(embeds));
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java
new file mode 100644
index 0000000..9ef63af
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/context/MessageCommandContext.java
@@ -0,0 +1,192 @@
+package me.bobert.netneutrality.core.context;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Breadcrumb;
+import io.sentry.Sentry;
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.thread.ThreadManager;
+import me.bobert.netneutrality.utils.Settings;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.*;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class MessageCommandContext implements CommandContext {
+
+ private static final ExecutorService commandExecutor = ThreadManager.getExecutor();
+
+ private AbstractCommand command;
+ private final MessageReceivedEvent event;
+ private final Message message;
+ private final Guild guild;
+ private final MessageChannel channel;
+ private final Member member;
+ private final User user;
+ private final JDA jda;
+
+ public MessageCommandContext(MessageReceivedEvent event, Message message, Member member, User user, Guild guild, MessageChannel channel, JDA jda) {
+ this.event = event;
+ this.message = message;
+ this.member = member;
+ this.guild = guild;
+ this.channel = channel;
+ this.user = user;
+ this.jda = jda;
+
+ io.sentry.protocol.User sentryUser = new io.sentry.protocol.User();
+ sentryUser.setId(member.getId());
+ sentryUser.setUsername(member.getEffectiveName());
+ Sentry.setUser(sentryUser);
+ Sentry.addBreadcrumb(Breadcrumb.info("New context for message id " + message.getId()));
+ }
+
+ public static MessageCommandContext createContext(MessageReceivedEvent event) {
+ Message message = event.getMessage();
+ Member member = event.getMember();
+ User user = event.getAuthor();
+ Guild guild = event.getGuild();
+ MessageChannel channel = event.getChannel();
+ JDA jda = event.getJDA();
+
+ return new MessageCommandContext(event, message, member, user, guild, channel, jda);
+ }
+
+ @Override
+ public MessageReceivedEvent getEvent() {
+ return event;
+ }
+
+ public String getArgument(int index) {
+ return getArgs()[index];
+ }
+
+ public String[] getArgs() {
+ String msg = message.getContentDisplay().trim();
+ msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
+ String cmd = msg.split(" ", 2)[0];
+ String[] args = msg.replaceFirst("(" + cmd + ")(\\s+)?", "").split("\s");
+
+ if (args.length == 1 && args[0].isEmpty())
+ args = new String[0];
+
+ return args;
+ }
+
+ public String getMessageText() {
+ String msg = message.getContentDisplay().trim();
+ msg = msg.substring(Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$").length()).trim();
+ String cmd = msg.split(" ", 2)[0];
+
+ return msg.replaceFirst("(" + cmd + ")(\s)?", "");
+ }
+
+ public Message getMessage() {
+ return message;
+ }
+
+ public Guild getGuild() {
+ return guild;
+ }
+
+ public MessageChannel getChannel() {
+ return channel;
+ }
+
+ public Member getGuildUser() {
+ return member;
+ }
+
+ public User getDiscordUser() {
+ return user;
+ }
+
+ public JDA getJDA() {
+ return jda;
+ }
+
+ public AbstractCommand getCommand() {
+ return command;
+ }
+
+ public GuildManager getGuildManager() {
+ return GuildManager.getManager(guild);
+ }
+
+ public void reply(String message) {
+ reply(MessageCreateData.fromContent(message));
+ }
+
+ public void reply(String format, Object... arguments) {
+ reply(String.format(format, arguments));
+ }
+
+ public void reply(MessageEmbed... embeds) {
+ reply(MessageCreateData.fromEmbeds(embeds));
+ }
+
+ public void reply(MessageCreateData msg) {
+ if (channel != null && channel.canTalk()) {
+ channel.sendMessage(msg).queue();
+ }
+ }
+
+ @Override
+ public void replyToMessage(String message) {
+ replyToMessage(MessageCreateData.fromContent(message));
+ }
+
+ @Override
+ public void replyToMessage(String format, Object... arguments) {
+ replyToMessage(String.format(format, arguments));
+ }
+
+ public void replyToMessage(MessageCreateData msg) {
+ if (channel != null && channel.canTalk() && message != null) {
+ message.reply(msg).queue();
+ }
+ }
+
+ public void replyPrivate(String message) {
+ replyPrivate(MessageCreateData.fromContent(message));
+ }
+
+ public void replyPrivate(MessageCreateData msg) {
+ user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue();
+ }
+
+ public void replyPrivate(MessageEmbed... embeds) {
+ replyPrivate(MessageCreateData.fromEmbeds(embeds));
+ }
+
+ public void replyPrivate(Collection extends MessageEmbed> embeds) {
+ user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue();
+ }
+
+ @Override
+ public void deleteMessage() {
+ if (message != null) {
+ message.delete().queueAfter(1, TimeUnit.SECONDS);
+ }
+ }
+
+ public boolean isMessageFromOwner() {
+ return member.isOwner();
+ }
+
+ public boolean isMessageFromCreator() {
+ String creatorId = Settings.getInstance().get("NETNEUTRALITY_BOT_OWNER");
+
+ return creatorId.equals(member.getId());
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java b/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java
new file mode 100644
index 0000000..35064c1
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/core/context/SlashCommandContext.java
@@ -0,0 +1,180 @@
+package me.bobert.netneutrality.core.context;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.command.AbstractCommand;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.interactions.InteractionHook;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+import net.dv8tion.jda.api.utils.messages.MessageEditData;
+
+import java.util.Collection;
+
+public class SlashCommandContext implements CommandContext {
+
+ private final SlashCommandInteractionEvent event;
+ private AbstractCommand command;
+ private final JDA jda;
+ private final Guild guild;
+ private final MessageChannel channel;
+ private final Member member;
+ private final User user;
+
+ private SlashCommandContext(JDA jda, SlashCommandInteractionEvent event, Guild guild, MessageChannel channel, Member member, User user) {
+ this.jda = jda;
+ this.event = event;
+ this.guild = guild;
+ this.channel = channel;
+ this.member = member;
+ this.user = user;
+ }
+
+ public static SlashCommandContext createContext(SlashCommandInteractionEvent event) {
+ Member member = event.getMember();
+ User user = event.getUser();
+ Guild guild = event.getGuild();
+ MessageChannel channel = event.getChannel();
+ JDA jda = event.getJDA();
+
+ return new SlashCommandContext(jda, event, guild, channel, member, user);
+ }
+
+ @Override
+ public SlashCommandInteractionEvent getEvent() {
+ return event;
+ }
+
+ @Override
+ public AbstractCommand getCommand() {
+ return command;
+ }
+
+ @Override
+ public JDA getJDA() {
+ return jda;
+ }
+
+ @Override
+ public Guild getGuild() {
+ return guild;
+ }
+
+ @Override
+ public MessageChannel getChannel() {
+ return channel;
+ }
+
+ @Override
+ public Member getGuildUser() {
+ return member;
+ }
+
+ @Override
+ public User getDiscordUser() {
+ return user;
+ }
+
+ @Override
+ public void deleteMessage() {
+ // not implemented
+ }
+
+ public void sendMessageInPrivate(String msg) {
+ sendMessageInPrivate(new MessageCreateBuilder().setContent(msg).build());
+ }
+
+ public void sendMessageInPrivate(MessageCreateData msg) {
+ user.openPrivateChannel().flatMap(channel -> channel.sendMessage(msg)).queue();
+ }
+
+ public void sendMessageInPrivate(MessageEmbed embed, MessageEmbed... embeds) {
+ user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embed, embeds)).queue();
+ }
+
+ public void sendMessageInPrivate(Collection extends MessageEmbed> embeds) {
+ user.openPrivateChannel().flatMap(channel -> channel.sendMessageEmbeds(embeds)).queue();
+ }
+
+ public void reply(MessageCreateData message) {
+ if (event.isAcknowledged()) {
+ getHook().editOriginal(MessageEditData.fromCreateData(message)).queue();
+ //getHook().sendMessage(message).setEphemeral(false).queue();
+ return;
+ }
+
+ event.reply(message).queue();
+ }
+
+ public void reply(MessageEmbed... embeds) {
+ reply(MessageCreateData.fromEmbeds(embeds));
+ }
+
+ @Override
+ public void reply(String message) {
+ reply(MessageCreateData.fromContent(message));
+ }
+
+ public void replyEphemeral(MessageCreateData message) {
+ if (event.isAcknowledged()) {
+ getHook().editOriginal(MessageEditData.fromCreateData(message)).queue();
+ //getHook().sendMessage(message).setEphemeral(true).queue();
+ return;
+ }
+
+ event.reply(message).setEphemeral(true).queue();
+ }
+
+ public void replyEphemeral(MessageEmbed... embeds) {
+ replyEphemeral(MessageCreateData.fromEmbeds(embeds));
+ }
+
+ public void replyEphemeral(String format, Object... arguments) {
+ replyEphemeral(String.format(format, arguments));
+ }
+
+ public void replyEphemeral(String message) {
+ replyEphemeral(MessageCreateData.fromContent(message));
+ }
+
+ @Override
+ public void replyToMessage(String message) {
+ replyEphemeral(message);
+ }
+
+ public InteractionHook getHook() {
+ if (!event.isAcknowledged())
+ throw new RuntimeException("Interaction isn't acknowledged yet");
+
+ return event.getHook();
+ }
+
+ public void ackAndWait(boolean ephemeral) {
+ if (event.isAcknowledged())
+ throw new RuntimeException("Interaction is already acknowledged, please use getHook() instead.");
+
+ event.deferReply(ephemeral).queue();
+ }
+
+ public void ackAndWait() {
+ ackAndWait(false);
+ }
+
+ public void ack() {
+ ackAndWait();
+ }
+
+ public void ephemeral() {
+ ackAndWait(true);
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/AbstractController.java b/src/main/java/me/bobert/netneutrality/db/AbstractController.java
new file mode 100644
index 0000000..6fad802
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/AbstractController.java
@@ -0,0 +1,23 @@
+package me.bobert.netneutrality.db;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.SessionManager;
+import org.hibernate.SessionFactory;
+
+public abstract class AbstractController {
+
+ protected SessionFactory sessionFactory;
+
+ public AbstractController() {
+ sessionFactory = SessionManager.getSessionFactory();
+ }
+
+ protected SessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/DBController.java b/src/main/java/me/bobert/netneutrality/db/DBController.java
new file mode 100644
index 0000000..aab0723
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/DBController.java
@@ -0,0 +1,38 @@
+package me.bobert.netneutrality.db;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.SessionManager;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class DBController {
+
+ private final static Logger logger = LogManager.getLogger(DBController.class);
+
+ public static T get(Class returnType) {
+ if (returnType == null)
+ throw new IllegalArgumentException("return type is null");
+
+ try {
+ return returnType.getConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void shutdown() {
+ SessionManager.shutdown();
+ }
+
+ public static void init() {
+ SessionManager.getSessionFactory();
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/GSettings.java b/src/main/java/me/bobert/netneutrality/db/GSettings.java
new file mode 100644
index 0000000..5945ff5
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/GSettings.java
@@ -0,0 +1,29 @@
+package me.bobert.netneutrality.db;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+public enum GSettings {
+ //TODO: description
+ MUSIC_ROLE("MUSIC_ROLE", ""),
+ MUSIC_CHANNEL("MUSIC_CHANNEL", ""),
+ MUSIC_VOLUME("MUSIC_VOLUME", "30");
+
+ private final String name;
+ private final String defaultValue;
+
+ GSettings(String name, String defaultValue) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java b/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java
new file mode 100644
index 0000000..19dea2a
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/controllers/DBBlacklist.java
@@ -0,0 +1,141 @@
+package me.bobert.netneutrality.db.controllers;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.db.AbstractController;
+import me.bobert.netneutrality.db.entities.OBlacklist;
+import me.bobert.netneutrality.db.entities.OGuild;
+import me.bobert.netneutrality.utils.JpaUtils;
+import net.dv8tion.jda.api.entities.Guild;
+import org.hibernate.Session;
+import org.hibernate.query.Query;
+
+import java.util.ArrayList;
+
+public class DBBlacklist extends AbstractController {
+
+ public void add(String word, CommandContext context) {
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+
+
+ OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
+
+ if (bl == null) {
+ Session session = getSessionFactory().openSession();
+
+ session.beginTransaction();
+ bl = new OBlacklist();
+ bl.setGuild(guildManager.getGuildEntity());
+ bl.setCreatorId(context.getGuildUser().getId());
+ bl.setWord(word);
+ bl.setEnabled(true);
+
+ session.persist(bl);
+ session.getTransaction().commit();
+ session.close();
+
+ guildManager.reloadBlacklistCache();
+ context.reply(String.format("Added the word [%s] to the blacklist.", word));
+ } else {
+ context.reply("Word is already blacklisted");
+ }
+
+
+
+ }
+
+ public void delete(String word, CommandContext context) {
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+ OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
+
+ if (bl != null) {
+ Session session = getSessionFactory().openSession();
+ session.beginTransaction();
+
+ session.remove(bl);
+ session.getTransaction().commit();
+ session.close();
+
+ guildManager.reloadBlacklistCache();
+ context.reply("Removed the word [%s] from the blacklist.", word);
+ } else {
+ context.reply("Can't delete word because it doesn't exist");
+ }
+ }
+
+ public void enable(String word, CommandContext context) {
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+
+
+ OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
+ if (bl != null && !bl.getEnabled()) {
+ Session session = getSessionFactory().openSession();
+ session.beginTransaction();
+
+ bl.setEnabled(true);
+
+ session.merge(bl);
+ session.getTransaction().commit();
+ session.close();
+
+ guildManager.reloadBlacklistCache();
+ context.reply("Enabled the word [%s] for the blacklist.", word);
+ } else {
+ context.reply("Either word doesn't exist or is already enabled");
+ }
+
+
+ }
+
+ public void disable(String word, CommandContext context) {
+ GuildManager guildManager = GuildManager.getManager(context.getGuild());
+
+
+ OBlacklist bl = getWordFromDb(word, context.getGuild().getId());
+ if (bl != null && bl.getEnabled()) {
+ Session session = getSessionFactory().openSession();
+ session.beginTransaction();
+
+ bl.setEnabled(false);
+
+ session.merge(bl);
+ session.getTransaction().commit();
+ session.close();
+
+ guildManager.reloadBlacklistCache();
+ context.reply("Disabled the word [%s] for the blacklist.", word);
+ } else {
+ context.reply("Either word doesn't exist or is already disabled");
+ }
+ }
+
+ public ArrayList getBlacklistForGuild(Guild guild) {
+ GuildManager guildManager = GuildManager.getManager(guild);
+ Session session = getSessionFactory().openSession();
+
+ OGuild guildEntity = guildManager.getGuildEntity();
+ JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
+
+ ArrayList blacklists = new ArrayList<>(guildEntity.getBlacklists());
+ session.close();
+
+ return blacklists;
+ }
+
+ private OBlacklist getWordFromDb(String word, String guildId) {
+ Session session = getSessionFactory().openSession();
+
+ Query query = session.createNamedQuery("OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", OBlacklist.class);
+ query.setParameter("guildId", guildId);
+ query.setParameter("word", word);
+ OBlacklist blacklist = query.uniqueResult();
+
+ session.close();
+ return blacklist;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java b/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java
new file mode 100644
index 0000000..81b1b38
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/controllers/DBMessage.java
@@ -0,0 +1,97 @@
+package me.bobert.netneutrality.db.controllers;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.db.AbstractController;
+import me.bobert.netneutrality.db.entities.OLogMessage;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Session;
+import org.hibernate.query.Query;
+
+import java.util.List;
+
+public class DBMessage extends AbstractController {
+
+ private final static Logger logger = LogManager.getLogger(DBMessage.class);
+
+ public void log(GuildManager guildManager, MessageChannel channel, Member author, Message message) {
+ Session session = getSessionFactory().openSession();
+ session.beginTransaction();
+
+ OLogMessage logMessage = new OLogMessage();
+ logMessage.setGuild(guildManager.getGuildEntity());
+ logMessage.setMessageId(message.getId());
+ logMessage.setMessage(message.getContentRaw().trim());
+ logMessage.setChannelId(channel.getId());
+ logMessage.setUserId(author.getId());
+ logMessage.setDateSent(message.getTimeCreated().toInstant());
+
+ session.persist(logMessage);
+ session.getTransaction().commit();
+ session.close();
+ }
+
+ public void logEdit(String messageId, String newMessage) {
+ Session session = getSessionFactory().openSession();
+ Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
+ query.setParameter("messageId", messageId);
+
+ OLogMessage message = query.uniqueResult();
+ if (message != null) {
+ session.beginTransaction();
+ message.setEditedMessage(newMessage);
+
+ session.merge(message);
+ session.getTransaction().commit();
+ }
+
+ session.close();
+ }
+
+ public OLogMessage getMessage(String messageId) {
+ Session session = getSessionFactory().openSession();
+ Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
+ query.setParameter("messageId", messageId);
+
+ OLogMessage message = query.uniqueResult();
+ session.close();
+
+ return message;
+ }
+
+ public List getMessagesFromUser(String userId, String guildId, int max) {
+ Session session = getSessionFactory().openSession();
+ Query query = session.createNamedQuery("OLogMessage.findByUserIdEquals", OLogMessage.class);
+ query.setParameter("guildId", guildId);
+ query.setParameter("userId", userId);
+ query.setMaxResults(max);
+
+ List messages = query.getResultList();
+ session.close();
+
+ return messages;
+ }
+
+ public String getMessageString(String messageId) {
+ Session session = getSessionFactory().openSession();
+ Query query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
+ query.setParameter("messageId", messageId);
+
+ OLogMessage message = query.uniqueResult();
+ session.close();
+
+ if (message == null)
+ return null;
+ else
+ return message.getMessage();
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java b/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java
new file mode 100644
index 0000000..5db94c6
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OBlacklist.java
@@ -0,0 +1,74 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "blacklist")
+@NamedQueries({
+ @NamedQuery(name = "OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", query = "select o from OBlacklist o where upper(o.word) = upper(:word) and o.guild.guildId = :guildId")
+})
+public class OBlacklist {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Integer id;
+
+ @Column(name = "word", nullable = false)
+ private String word;
+
+ @Column(name = "enabled", nullable = false)
+ private Boolean enabled = false;
+
+ @Column(name = "creator_id", nullable = false)
+ private String creatorId;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "guild_id", nullable = false)
+ private OGuild guild;
+
+ public OGuild getGuild() {
+ return guild;
+ }
+
+ public void setGuild(OGuild guild) {
+ this.guild = guild;
+ }
+
+ public String getCreatorId() {
+ return creatorId;
+ }
+
+ public void setCreatorId(String creatorId) {
+ this.creatorId = creatorId;
+ }
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public String getWord() {
+ return word;
+ }
+
+ public void setWord(String word) {
+ this.word = word;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OGuild.java b/src/main/java/me/bobert/netneutrality/db/entities/OGuild.java
new file mode 100644
index 0000000..021b560
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OGuild.java
@@ -0,0 +1,118 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.*;
+
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+
+@Entity
+@Table(name = "guilds")
+public class OGuild {
+
+ @Id
+ @Column(name = "guild_id", nullable = false)
+ private String guildId;
+
+ @Column(name = "logging", nullable = false)
+ private Boolean logging = false;
+
+ @Column(name = "debug", nullable = false)
+ private Boolean debug = true;
+
+ @Column(name = "log_channel")
+ private String logChannel;
+
+ @OneToMany(mappedBy = "guild")
+ private Set guildSettings = new LinkedHashSet<>();
+
+ @OneToMany(mappedBy = "guild")
+ private Set blacklists = new LinkedHashSet<>();
+
+ @OneToMany(mappedBy = "guild")
+ private Set logMessages = new LinkedHashSet<>();
+
+ @OneToMany(mappedBy = "guild")
+ private Set OLoggings = new LinkedHashSet<>();
+
+ public Set getLoggings() {
+ return OLoggings;
+ }
+
+ public void setLoggings(Set OLoggings) {
+ this.OLoggings = OLoggings;
+ }
+
+ public Set getLogMessages() {
+ return logMessages;
+ }
+
+ public void setLogMessages(Set logMessages) {
+ this.logMessages = logMessages;
+ }
+
+ public Set getBlacklists() {
+ return blacklists;
+ }
+
+ public void setBlacklists(Set blacklists) {
+ this.blacklists = blacklists;
+ }
+
+ public Set getGuildSettings() {
+ return guildSettings;
+ }
+
+ public void setGuildSettings(Set guildSettings) {
+ this.guildSettings = guildSettings;
+ }
+
+ public String getLogChannel() {
+ return logChannel;
+ }
+
+ public void setLogChannel(String logChannel) {
+ this.logChannel = logChannel;
+ }
+
+ public Boolean getLogging() {
+ return logging;
+ }
+
+ public void setLogging(Boolean logging) {
+ this.logging = logging;
+ }
+
+ public Boolean getDebug() {
+ return debug;
+ }
+
+ public void setDebug(Boolean debug) {
+ this.debug = debug;
+ }
+
+
+ public String getGuildId() {
+ return guildId;
+ }
+
+ public void setGuildId(String guildId) {
+ this.guildId = guildId;
+ }
+
+ public OGuildSetting getSettingByName(String name) {
+ Optional setting = getGuildSettings()
+ .stream()
+ .filter(setting1 -> setting1.getName().equalsIgnoreCase(name))
+ .findFirst();
+
+ return setting.orElse(null);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OGuildSetting.java b/src/main/java/me/bobert/netneutrality/db/entities/OGuildSetting.java
new file mode 100644
index 0000000..a868311
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OGuildSetting.java
@@ -0,0 +1,64 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "guild_settings")
+@NamedQueries({
+ @NamedQuery(name = "settings_findByName", query = "select o from OGuildSetting o where o.guild.id = :id and upper(o.name) = upper(:name)")
+})
+public class OGuildSetting {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Integer id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Lob
+ @Column(name = "value", nullable = false)
+ private String value;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "guild_id", nullable = false)
+ private OGuild guild;
+
+ public OGuild getGuild() {
+ return guild;
+ }
+
+ public void setGuild(OGuild guild) {
+ this.guild = guild;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java b/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java
new file mode 100644
index 0000000..e8b8a82
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OLogMessage.java
@@ -0,0 +1,111 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.*;
+
+import java.time.Instant;
+
+@Entity
+@Table(name = "log_message")
+@NamedQueries({
+ @NamedQuery(name = "OLogMessage.findByMessageIdEquals", query = "select o from OLogMessage o where o.messageId = :messageId"),
+ @NamedQuery(name = "OLogMessage.findByGuild_GuildIdEqualsAndUserIdEquals", query = "select o from OLogMessage o where o.guild.guildId = :guildId and o.userId = :userId")
+})
+public class OLogMessage {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Integer id;
+
+ @Column(name = "channel_id", nullable = false)
+ private String channelId;
+
+ @Column(name = "message_id", nullable = false)
+ private String messageId;
+
+ @Column(name = "user_id", nullable = false)
+ private String userId;
+
+ @Convert(disableConversion = true)
+ @Column(name = "date_sent", nullable = false)
+ private Instant dateSent;
+
+ @Column(name = "message", length = 2000)
+ private String message;
+
+ @Column(name = "edited_message", length = 2000)
+ private String editedMessage;
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "guild_id", nullable = false)
+ private OGuild guild;
+
+ public OGuild getGuild() {
+ return guild;
+ }
+
+ public void setGuild(OGuild guild) {
+ this.guild = guild;
+ }
+
+ public String getEditedMessage() {
+ return editedMessage;
+ }
+
+ public void setEditedMessage(String editedMessage) {
+ this.editedMessage = editedMessage;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Instant getDateSent() {
+ return dateSent;
+ }
+
+ public void setDateSent(Instant dateSent) {
+ this.dateSent = dateSent;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getMessageId() {
+ return messageId;
+ }
+
+ public void setMessageId(String messageId) {
+ this.messageId = messageId;
+ }
+
+ public String getChannelId() {
+ return channelId;
+ }
+
+ public void setChannelId(String channelId) {
+ this.channelId = channelId;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OLogging.java b/src/main/java/me/bobert/netneutrality/db/entities/OLogging.java
new file mode 100644
index 0000000..7dd2992
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OLogging.java
@@ -0,0 +1,48 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "logging")
+public class OLogging {
+ @EmbeddedId
+ private OLoggingId id;
+
+ @MapsId("guildId")
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "guild_id", nullable = false)
+ private OGuild guild;
+
+ @Column(name = "enabled", nullable = false)
+ private Boolean enabled = false;
+
+ public Boolean getEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(Boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public OGuild getGuild() {
+ return guild;
+ }
+
+ public void setGuild(OGuild guild) {
+ this.guild = guild;
+ }
+
+ public OLoggingId getId() {
+ return id;
+ }
+
+ public void setId(OLoggingId id) {
+ this.id = id;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/db/entities/OLoggingId.java b/src/main/java/me/bobert/netneutrality/db/entities/OLoggingId.java
new file mode 100644
index 0000000..9cf3671
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/db/entities/OLoggingId.java
@@ -0,0 +1,55 @@
+package me.bobert.netneutrality.db.entities;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import org.hibernate.Hibernate;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Objects;
+
+@Embeddable
+public class OLoggingId implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 8216581265618800768L;
+ @Column(name = "guild_id", nullable = false)
+ private String guildId;
+ @Column(name = "channel_id", nullable = false)
+ private String channelId;
+
+ public String getChannelId() {
+ return channelId;
+ }
+
+ public void setChannelId(String channelId) {
+ this.channelId = channelId;
+ }
+
+ public String getGuildId() {
+ return guildId;
+ }
+
+ public void setGuildId(String guildId) {
+ this.guildId = guildId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(guildId, channelId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
+ OLoggingId entity = (OLoggingId) o;
+ return Objects.equals(this.guildId, entity.guildId) &&
+ Objects.equals(this.channelId, entity.channelId);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java b/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java
new file mode 100644
index 0000000..c006425
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/exceptions/CommandException.java
@@ -0,0 +1,24 @@
+package me.bobert.netneutrality.exceptions;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.context.CommandContext;
+
+public abstract class CommandException extends RuntimeException {
+
+ public static final String DEFAULT_MESSAGE = "Unknown error during command execution.";
+ private final CommandContext context;
+
+ public CommandException(String message, CommandContext context, Throwable cause) {
+ super(message == null ? DEFAULT_MESSAGE : message, cause);
+ this.context = context;
+ }
+
+ public CommandContext getCommandContext() {
+ return context;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java
new file mode 100644
index 0000000..0639059
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/exceptions/CommandExecutionException.java
@@ -0,0 +1,24 @@
+package me.bobert.netneutrality.exceptions;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.context.CommandContext;
+
+/**
+ * Logs the error internally, without sending a message
+ */
+public class CommandExecutionException extends CommandException {
+
+ /**
+ * Logs the error internally, without sending a message
+ * @param context The command context
+ */
+ public CommandExecutionException(String message, CommandContext context, Throwable cause) {
+ super(message, context, cause);
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/exceptions/ExecutorException.java b/src/main/java/me/bobert/netneutrality/exceptions/ExecutorException.java
new file mode 100644
index 0000000..2704a27
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/exceptions/ExecutorException.java
@@ -0,0 +1,17 @@
+package me.bobert.netneutrality.exceptions;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.LogHandler;
+
+public class ExecutorException implements Thread.UncaughtExceptionHandler {
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ LogHandler.logThread(t, e);
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java b/src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java
new file mode 100644
index 0000000..1cf33e0
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/SlashCommandHooks.java
@@ -0,0 +1,25 @@
+package me.bobert.netneutrality.hooks;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.CommandProcessor;
+import me.bobert.netneutrality.core.context.SlashCommandContext;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class SlashCommandHooks extends ListenerAdapter {
+
+ @Override
+ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) {
+ String commandName = event.getName();
+
+ SlashCommandContext commandContext = SlashCommandContext.createContext(event);
+ CommandProcessor processor = new CommandProcessor(commandName, commandContext);
+ processor.dispatch();
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java b/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java
new file mode 100644
index 0000000..ebdb410
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/guilds/ServerJoinHandler.java
@@ -0,0 +1,26 @@
+package me.bobert.netneutrality.hooks.guilds;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.GuildManager;
+import net.dv8tion.jda.api.events.guild.GuildJoinEvent;
+import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class ServerJoinHandler extends ListenerAdapter {
+
+ @Override
+ public void onGuildJoin(@NotNull GuildJoinEvent event) {
+ GuildManager.getManager(event.getGuild());
+ }
+
+ @Override
+ public void onGuildLeave(@NotNull GuildLeaveEvent event) {
+ GuildManager.getManager(event.getGuild()).removeFromDB();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java
new file mode 100644
index 0000000..3fab697
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/message/EditHandler.java
@@ -0,0 +1,58 @@
+package me.bobert.netneutrality.hooks.message;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.SessionManager;
+import me.bobert.netneutrality.db.DBController;
+import me.bobert.netneutrality.db.controllers.DBMessage;
+import me.bobert.netneutrality.db.entities.OGuild;
+import me.bobert.netneutrality.utils.JpaUtils;
+import me.bobert.netneutrality.utils.MessageFunction;
+import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
+import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.hibernate.Session;
+
+import java.util.stream.Collectors;
+
+public class EditHandler extends ListenerAdapter {
+
+ @Override
+ public void onMessageUpdate(MessageUpdateEvent event) {
+
+ if (event.getAuthor().isBot()) return;
+
+ GuildManager guildManager = GuildManager.getManager(event.getGuild());
+
+ if (guildManager.getGuildEntity().getLogging()) {
+ OGuild guildEntity = guildManager.getGuildEntity();
+
+ if (guildEntity.getLogChannel() != null && !guildEntity.getLogChannel().isEmpty()) {
+ Session session = SessionManager.getSessionFactory().openSession();
+ JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
+
+ int size = guildEntity.getLoggings()
+ .stream()
+ .filter(oLogging -> oLogging.getId().getChannelId().equals(event.getChannel().getId()) && oLogging.getEnabled())
+ .collect(Collectors.toSet())
+ .size();
+
+ if (size > 0) {
+ TextChannel channel = event.getGuild().getTextChannelById(guildEntity.getLogChannel());
+
+ if (channel != null) {
+ String originalMessage = DBController.get(DBMessage.class).getMessageString(event.getMessage().getId());
+ DBController.get(DBMessage.class).logEdit(event.getMessage().getId(), event.getMessage().getContentDisplay());
+
+ channel.sendMessageEmbeds(MessageFunction.buildEditMessage(event.getMember(), event.getMessage(), originalMessage)).queue();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java
new file mode 100644
index 0000000..7defada
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/message/MessageReceivedHandler.java
@@ -0,0 +1,43 @@
+package me.bobert.netneutrality.hooks.message;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.CommandProcessor;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.context.MessageCommandContext;
+import me.bobert.netneutrality.utils.Settings;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class MessageReceivedHandler extends ListenerAdapter {
+
+ @Override
+ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
+ if (event.getAuthor().isBot()) return;
+ if (event.isWebhookMessage()) return;
+
+ if (!event.isFromGuild())
+ return;
+
+ Message message = event.getMessage();
+
+ MessageCommandContext context = MessageCommandContext.createContext(event);
+
+ String rawMessage = message.getContentDisplay().trim();
+ String prefix = Settings.getInstance().get("NETNEUTRALITY_BOT_PREFIX", "$");
+
+ if (rawMessage.startsWith(prefix)) {
+ rawMessage = rawMessage.replaceFirst(String.format("^[%s\\s]{1,}", prefix), "").trim();
+ // rawMessage = rawMessage.substring(prefix.length()).trim();
+ String commandString = rawMessage.split(" ", 2)[0];
+
+ CommandProcessor processor = new CommandProcessor(commandString, context);
+ processor.dispatch();
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java b/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java
new file mode 100644
index 0000000..bcbae8c
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/message/ReactionHandler.java
@@ -0,0 +1,31 @@
+package me.bobert.netneutrality.hooks.message;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
+import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
+import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class ReactionHandler extends ListenerAdapter {
+
+ @Override
+ public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
+ Member member = event.getMember();
+ }
+
+ @Override
+ public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) {
+
+ }
+
+ @Override
+ public void onMessageUpdate(@NotNull MessageUpdateEvent event) {
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java b/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java
new file mode 100644
index 0000000..00543ca
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/hooks/voice/VoiceEventHandler.java
@@ -0,0 +1,18 @@
+package me.bobert.netneutrality.hooks.voice;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public class VoiceEventHandler extends ListenerAdapter {
+
+ @Override
+ public void onGuildVoiceUpdate(@NotNull GuildVoiceUpdateEvent event) {
+ super.onGuildVoiceUpdate(event);
+ }
+}
diff --git a/src/main/java/netneutrality/http/WebModule.java b/src/main/java/me/bobert/netneutrality/http/WebModule.java
similarity index 54%
rename from src/main/java/netneutrality/http/WebModule.java
rename to src/main/java/me/bobert/netneutrality/http/WebModule.java
index 22d6bb5..03a2750 100644
--- a/src/main/java/netneutrality/http/WebModule.java
+++ b/src/main/java/me/bobert/netneutrality/http/WebModule.java
@@ -1,8 +1,17 @@
-package netneutrality.http;
+package me.bobert.netneutrality.http;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Breadcrumb;
+import io.sentry.Sentry;
+import me.bobert.netneutrality.NetNeutrality;
+import me.bobert.netneutrality.core.LogHandler;
import okhttp3.*;
import org.json.JSONArray;
-import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
@@ -12,9 +21,12 @@ import java.util.TreeMap;
public class WebModule {
- private String url;
- private OkHttpClient client = new OkHttpClient();
+ private final String url;
+ private final OkHttpClient client = new OkHttpClient();
private Response response;
+ //private final String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 OPR/82.0.4227.43";
+ private static final String userAgent = String.format("Net Neutrality/%s (Discord bot, tony@bobert.me)", NetNeutrality.VERSION);
+
public WebModule(String url) {
this.url = url;
@@ -27,23 +39,24 @@ public class WebModule {
* @return an instance of the WebModule class with the Response of the Web Request
*/
public WebModule request() {
-
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "get"));
Request request = new Request.Builder().url(this.url)
- .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36")
+ .addHeader("User-Agent", userAgent)
.build();
try {
response = client.newCall(request).execute();
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "get", response.code()));
} catch (IOException e) {
- e.printStackTrace();
+ Sentry.captureException(e);
}
return this;
}
- public WebModule request(Map headers) throws IOException {
-
+ public WebModule request(Map headers) {
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "get"));
Request.Builder builder = new Request.Builder().url(this.url)
- .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36");
+ .addHeader("User-Agent", userAgent);
for (Map.Entry header : headers.entrySet()) {
builder.addHeader(header.getKey(), header.getValue());
@@ -51,28 +64,37 @@ public class WebModule {
Request request = builder.build();
- response = client.newCall(request).execute();
+ try {
+ response = client.newCall(request).execute();
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "get", response.code()));
+ } catch (IOException e) {
+ Sentry.captureException(e);
+ }
return this;
}
- public WebModule post(TreeMap params, Map headers) throws IOException {
- RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), createJson(params));
+ public WebModule post(TreeMap params, Map headers) {
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "post"));
+ RequestBody body = RequestBody.create(createJson(params), MediaType.parse("application/json; charset=utf-8"));
Request.Builder builder = new Request.Builder().url(this.url)
- .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36");
+ .addHeader("User-Agent", userAgent);
for (Map.Entry header : headers.entrySet()) {
builder.addHeader(header.getKey(), header.getValue());
}
Request request = builder.post(body).build();
- response = client.newCall(request).execute();
+ try {
+ response = client.newCall(request).execute();
+ Sentry.addBreadcrumb(Breadcrumb.http(url, "post", response.code()));
+ } catch (IOException e) {
+ Sentry.captureException(e);
+ }
return this;
}
public static String createJson(TreeMap params) {
-
-
StringBuilder json = new StringBuilder();
for (Map.Entry param : params.entrySet()) {
@@ -85,6 +107,21 @@ public class WebModule {
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "}" : "");
}
+ public static String createJsonObject(List key, List value) {
+ if (key.size() != value.size())
+ throw new IllegalArgumentException("Key list size not equal to value list size");
+
+ StringBuilder json = new StringBuilder();
+
+ for (int index = 0; index < key.size();) {
+ json.append(String.format("\"%s\":\"%s\"", key.get(index), value.get(index)));
+ json.append(",");
+ }
+ json.insert(0, "{");
+
+ return (json.length() > 1 ? json.substring(0, json.length() - 1) + "}" : "");
+ }
+
public static String createJsonArray(List list) {
StringBuilder json = new StringBuilder();
@@ -109,37 +146,44 @@ public class WebModule {
return (json.length() > 1 ? json.substring(0, json.length() - 1) + "]" : "[]");
}
+
/**
* @return the response as a JSONObject
- * @throws JSONException
- * @throws IOException
*/
- public JSONObject getAsObject() throws JSONException, IOException {
+ public JSONObject getAsObject() {
if (response == null)
throw new RuntimeException("Request has not been sent");
-
- return new JSONObject(response.body().string());
+
+ try {
+ ResponseBody body = response.body();
+ return new JSONObject(body.string());
+ } catch (IOException | NullPointerException e) {
+ LogHandler.log(e);
+ return new JSONObject();
+ }
}
/**
* Use if your response start with an array : [ ]
*
* @return the response as a JSONArray
- * @throws JSONException
- * @throws IOException
*/
- public JSONArray getAsArray() throws JSONException, IOException {
+ public JSONArray getAsArray() {
if (response == null)
throw new RuntimeException("Request has not been sent");
-
- return new JSONArray(response.body().string());
+
+ try {
+ ResponseBody body = response.body();
+ return new JSONArray(body.string());
+ } catch (IOException | NullPointerException e) {
+ LogHandler.log(e);
+ return new JSONArray();
+ }
}
/**
* @return the response as a String
- * @throws JSONException
- * @throws IOException
*/
public String getRawData() throws IOException {
if (response == null)
@@ -148,29 +192,4 @@ public class WebModule {
return response.body().string();
}
- public String getTextInHtmlTag(String tag) {
- String body = "";
- try {
- body = getRawData();
- } catch (IOException | RuntimeException e) {
- e.printStackTrace();
- }
- int startOfTag = body.indexOf(tag);
- int endOfTag = body.indexOf(getEndHtmlTag(tag));
- if (startOfTag < 0 || endOfTag < 0) {
- return "";
- }
-
- return body.substring(startOfTag + tag.length(), endOfTag - (tag.length() - 1));
- }
-
- private String getEndHtmlTag(String startingTag) {
- String endingTag = "";
- if (startingTag.charAt(0) == '<' && startingTag.charAt(1) != '/' ) {
- endingTag = "" + startingTag.substring(1, startingTag.length() - 1) + ">";
- }
- System.out.println("endingTag = " + endingTag);
-
- return endingTag;
- }
}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/AbstractHandler.java b/src/main/java/me/bobert/netneutrality/http/server/AbstractHandler.java
new file mode 100644
index 0000000..21f9300
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/http/server/AbstractHandler.java
@@ -0,0 +1,33 @@
+package me.bobert.netneutrality.http.server;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public abstract class AbstractHandler implements HttpHandler {
+
+ public abstract void handle(HttpExchange exchange) throws IOException;
+
+ public void sendStringResponse(HttpExchange exchange, String str) throws IOException {
+ exchange.sendResponseHeaders(200, str.length());
+ OutputStream os = exchange.getResponseBody();
+ os.write(str.getBytes());
+ os.close();
+ }
+
+ public void sendJsonResponse(HttpExchange exchange, String str) throws IOException {
+ exchange.getResponseHeaders().add("Content-Type", "application/json");
+ exchange.sendResponseHeaders(200, str.length());
+ OutputStream os = exchange.getResponseBody();
+ os.write(str.getBytes());
+ os.close();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java b/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java
new file mode 100644
index 0000000..a2b8f65
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/http/server/ActionHandler.java
@@ -0,0 +1,88 @@
+package me.bobert.netneutrality.http.server;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import me.bobert.netneutrality.NetNeutrality;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.http.WebModule;
+import net.dv8tion.jda.api.entities.Guild;
+import org.apache.logging.log4j.LogManager;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class ActionHandler extends AbstractHandler {
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+
+ String path = exchange.getRequestURI().getRawPath();
+
+ if (path.equals("/actions/shutdown")) {
+ LogManager.getLogger(NetNeutrality.class).info("Request to shutdown received");
+ NetNeutrality.shutdown();
+ sendStringResponse(exchange, "Shutting down..");
+
+ System.exit(0);
+ return;
+ }
+
+ if (path.equals("/actions/status")) {
+ TreeMap json = new TreeMap<>();
+ json.put("health", String.valueOf(NetNeutrality.getJDA().getStatus()));
+ json.put("ping", String.valueOf(NetNeutrality.getJDA().getGatewayPing()));
+
+ sendJsonResponse(exchange, WebModule.createJson(json));
+ return;
+ }
+
+ if (path.startsWith("/actions/settings/current/")) {
+ JSONObject json = new JSONObject();
+ String[] args = exchange.getRequestURI().toString().split("/");
+ String guildId = args[args.length - 1];
+
+ Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
+ if (guild != null) {
+ GuildManager manager = GuildManager.getManager(guild);
+ Map settings = manager.getGuildConfigManager().getSettings();
+ JSONObject setting = new JSONObject(settings);
+
+ json.put("status", "ok");
+ json.put("settings", setting);
+ } else {
+ json.put("status", "error");
+ }
+
+ sendJsonResponse(exchange, json.toString());
+ return;
+ }
+
+ if (path.startsWith("/actions/settings/reload/")) {
+ TreeMap json = new TreeMap<>();
+ String[] args = exchange.getRequestURI().toString().split("/");
+ String guildId = args[args.length - 1];
+
+ Guild guild = NetNeutrality.getJDA().getGuildById(guildId);
+ boolean ok = true; //TODO: add bool
+ if (guild == null)
+ GuildManager.getManager(guildId).getGuildConfigManager().reloadSettingsCache();
+ else
+ GuildManager.getManager(guild).getGuildConfigManager().reloadSettingsCache();
+
+
+ json.put("status", String.valueOf(ok));
+ sendJsonResponse(exchange, WebModule.createJson(json));
+
+ return;
+ }
+
+ sendStringResponse(exchange, path);
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/GuildHandler.java b/src/main/java/me/bobert/netneutrality/http/server/GuildHandler.java
new file mode 100644
index 0000000..26016b8
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/http/server/GuildHandler.java
@@ -0,0 +1,94 @@
+package me.bobert.netneutrality.http.server;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import me.bobert.netneutrality.NetNeutrality;
+import net.dv8tion.jda.api.JDA;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+public class GuildHandler extends AbstractHandler {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+
+ String path = exchange.getRequestURI().getRawPath();
+
+ if (path.equals("/guilds/owner")) {
+
+ JSONArray array = new JSONArray();
+
+ JDA jda = NetNeutrality.getJDA();
+
+ jda.getGuilds().forEach(guild -> {
+ JSONObject obj = new JSONObject();
+ obj.put("guild_id", guild.getId());
+ obj.put("owner_id", guild.getOwnerId());
+ array.put(obj);
+ });
+
+ sendJsonResponse(exchange, array.toString());
+ return;
+
+ }
+
+ if (path.startsWith("/guilds/channels/")) {
+
+ String[] args = exchange.getRequestURI().toString().split("/");
+ String guild = args[args.length - 1];
+ JSONArray array = new JSONArray();
+
+ JDA jda = NetNeutrality.getJDA();
+
+ if (jda.getGuildById(guild) == null) {
+ sendStringResponse(exchange, path);
+ return;
+ }
+
+ jda.getGuildById(guild).getTextChannels().forEach(channel -> {
+ JSONObject obj = new JSONObject();
+ obj.put("channel_id", channel.getId());
+ obj.put("channel_name", channel.getName());
+ array.put(obj);
+ });
+
+ sendJsonResponse(exchange, array.toString());
+ return;
+
+ }
+
+ if (path.startsWith("/guilds/roles/")) {
+
+ String[] args = exchange.getRequestURI().toString().split("/");
+ String guild = args[args.length - 1];
+ JSONArray array = new JSONArray();
+ JDA jda = NetNeutrality.getJDA();
+
+ if (jda.getGuildById(guild) == null) {
+ sendStringResponse(exchange, path);
+ return;
+ }
+
+ jda.getGuildById(guild).getRoles().forEach(role -> {
+ JSONObject obj = new JSONObject();
+ obj.put("channel_id", role.getId());
+ obj.put("channel_name", role.getName());
+ array.put(obj);
+ });
+
+ sendJsonResponse(exchange, array.toString());
+ return;
+
+ }
+
+ sendStringResponse(exchange, path);
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/RootHandler.java b/src/main/java/me/bobert/netneutrality/http/server/RootHandler.java
new file mode 100644
index 0000000..6d8a8d1
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/http/server/RootHandler.java
@@ -0,0 +1,22 @@
+package me.bobert.netneutrality.http.server;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+
+import java.io.IOException;
+
+public class RootHandler extends AbstractHandler {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+
+ String response = "
Server start success if you see this message
";
+
+ sendStringResponse(exchange, response);
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/http/server/WebApi.java b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java
new file mode 100644
index 0000000..216c7c1
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/http/server/WebApi.java
@@ -0,0 +1,46 @@
+package me.bobert.netneutrality.http.server;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import me.bobert.netneutrality.thread.BaseThreadFactory;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class WebApi {
+
+ private final static Logger logger = LogManager.getLogger(WebApi.class);
+
+ private HttpServer server;
+
+ public WebApi(int port) {
+ logger.info("Starting web server...");
+
+ try {
+ InetSocketAddress sockAddr = new InetSocketAddress(port);
+ server = HttpServer.create(sockAddr, 0);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ server.createContext("/", new RootHandler());
+ server.createContext("/actions", new ActionHandler());
+ server.createContext("/guilds", new GuildHandler());
+ ExecutorService executor = Executors.newFixedThreadPool(10, new BaseThreadFactory("web-api"));
+ server.setExecutor(executor);
+ server.start();
+ logger.info("Web server started at [{}]", server.getAddress());
+
+ }
+
+}
+
diff --git a/src/main/java/me/bobert/netneutrality/music/AudioPlayerSendHandler.java b/src/main/java/me/bobert/netneutrality/music/AudioPlayerSendHandler.java
new file mode 100644
index 0000000..9307f62
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/AudioPlayerSendHandler.java
@@ -0,0 +1,45 @@
+package me.bobert.netneutrality.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame;
+import net.dv8tion.jda.api.audio.AudioSendHandler;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This is a wrapper around AudioPlayer which makes it behave as an AudioSendHandler for JDA. As JDA calls canProvide
+ * before every call to provide20MsAudio(), we pull the frame in canProvide() and use the frame we already pulled in
+ * provide20MsAudio().
+ */
+public class AudioPlayerSendHandler implements AudioSendHandler {
+ private final AudioPlayer audioPlayer;
+ private AudioFrame lastFrame;
+
+ /**
+ * @param audioPlayer Audio player to wrap.
+ */
+ public AudioPlayerSendHandler(AudioPlayer audioPlayer) {
+ this.audioPlayer = audioPlayer;
+ }
+
+ @Override
+ public boolean canProvide() {
+ lastFrame = audioPlayer.provide();
+ return lastFrame != null;
+ }
+
+ @Override
+ public ByteBuffer provide20MsAudio() {
+ return ByteBuffer.wrap(lastFrame.getData());
+ }
+
+ @Override
+ public boolean isOpus() {
+ return true;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/MusicManager.java b/src/main/java/me/bobert/netneutrality/music/MusicManager.java
new file mode 100644
index 0000000..4b69cbc
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/MusicManager.java
@@ -0,0 +1,242 @@
+package me.bobert.netneutrality.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
+import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
+import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager;
+import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import dev.lavalink.youtube.YoutubeAudioSourceManager;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.db.GSettings;
+import me.bobert.netneutrality.exceptions.CommandExecutionException;
+import me.bobert.netneutrality.music.source.UdioAudioSourceManager;
+import me.bobert.netneutrality.thread.MusicPlayerAutoDisconnectThread;
+import me.bobert.netneutrality.thread.ThreadManager;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class MusicManager {
+
+ private final static Logger logger = LogManager.getLogger(MusicManager.class);
+ private final static Map managers = new ConcurrentHashMap<>();
+ private final static ScheduledExecutorService executor = ThreadManager.getMusicExecutor();
+
+ private final static AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
+ private final Guild guild;
+ private final AudioPlayer player;
+ private final TrackScheduler scheduler;
+ private ScheduledFuture> autoDisconnectThread;
+ private final int baseVolume;
+
+ private MusicManager(Guild guild) {
+ this.guild = guild;
+ this.player = playerManager.createPlayer();
+ this.scheduler = new TrackScheduler(this);
+ this.baseVolume = Integer.parseInt(GuildManager.getManager(guild).getGuildConfigManager().getSetting(GSettings.MUSIC_VOLUME));
+
+ guild.getAudioManager().setSendingHandler(new AudioPlayerSendHandler(player));
+ player.addListener(scheduler);
+
+ managers.put(guild.getId(), this);
+ }
+
+ public static MusicManager get(Guild guild) {
+ if (managers.containsKey(guild.getId())) {
+ return managers.get(guild.getId());
+ } else {
+ return new MusicManager(guild);
+ }
+ }
+
+ public static void initPlayerManager() {
+ playerManager.registerSourceManager(new YoutubeAudioSourceManager());
+ playerManager.registerSourceManager(SoundCloudAudioSourceManager.createDefault());
+ playerManager.registerSourceManager(new UdioAudioSourceManager());
+ playerManager.registerSourceManager(new HttpAudioSourceManager());
+ }
+
+ public boolean isNotConnectedTo(VoiceChannel vChannel) {
+ return vChannel == null || !vChannel.equals(guild.getAudioManager().getConnectedChannel());
+ }
+
+ public synchronized void connect(VoiceChannel vChannel) {
+ if (vChannel != null && isNotConnectedTo(vChannel)) {
+ logger.debug("Guild {}: connecting to voice channel {}", vChannel.getGuild().getId(), vChannel.getId());
+ guild.getAudioManager().openAudioConnection(vChannel);
+ guild.getAudioManager().setSelfDeafened(true);
+ player.setVolume(baseVolume);
+
+ if (autoDisconnectThread == null)
+ autoDisconnectThread = executor.scheduleWithFixedDelay(new MusicPlayerAutoDisconnectThread(this), 240, 240, TimeUnit.SECONDS);
+ } else {
+ throw new RuntimeException("Error connecting to voice channel");
+ }
+ }
+
+ public boolean isConnected() {
+ return guild != null && guild.getAudioManager().getConnectedChannel() != null;
+ }
+
+ public synchronized AudioTrack getCurrentlyPlaying() {
+ return player.getPlayingTrack();
+ }
+
+ public synchronized boolean isCurrentlyPlaying() {
+ return player.getPlayingTrack() != null;
+ }
+
+ public synchronized Member getRequesterOfCurrentTrack() {
+ QueueManager.TrackInfo info = scheduler.getQueueManager().peekHeadOfQueue();
+
+ if (info == null)
+ return null;
+
+ return info.getRequester();
+ }
+
+ protected AudioPlayer getPlayer() {
+ return player;
+ }
+
+ public Guild getGuild() {
+ return guild;
+ }
+
+ public synchronized void leave() {
+ if (guild != null && guild.getAudioManager().getConnectedChannel() != null) {
+ logger.debug("Guild {}: leaving voice channel {}", guild.getId(), guild.getAudioManager().getConnectedChannel().getId());
+ player.stopTrack();
+ guild.getAudioManager().closeAudioConnection();
+ }
+
+ scheduler.detachTextChannel();
+
+ if (autoDisconnectThread != null) {
+ autoDisconnectThread.cancel(true);
+ autoDisconnectThread = null;
+ }
+ }
+
+ public static void disconnectPlayers() {
+ for (MusicManager mm : managers.values()) {
+ mm.getScheduler().getQueueManager().clearQueue();
+ mm.player.destroy();
+ }
+ }
+
+ public synchronized void play(String query, CommandContext context, boolean needToBeConnected, boolean silent) {
+ logger.debug("Guild {}: request to play '{}' by user {}", guild.getId(), query, context.getGuildUser().getId());
+
+ Member member = context.getGuildUser();
+ MessageChannel channel = context.getChannel();
+
+ if (member.getVoiceState() == null) {
+ logger.error("Guild {}: Voice state is null", guild.getId());
+ context.replyToMessage("Voice state is null, something has gone terribly wrong.");
+ LogHandler.log(new CommandExecutionException("Voice state is null", context, null));
+
+ return;
+ }
+
+ if (!member.getVoiceState().inAudioChannel()) {
+ context.replyToMessage("You are not in a voice channel");
+ return;
+ }
+
+ VoiceChannel vChannel = (VoiceChannel) member.getVoiceState().getChannel();
+ if (needToBeConnected && isNotConnectedTo(vChannel)) {
+ context.replyToMessage("The player is not in your voice channel");
+ return;
+ }
+
+ if (isNotConnectedTo(vChannel)) {
+ connect(vChannel);
+ scheduler.attachTextChannel(channel);
+ }
+
+ SourceFinder.AudioSourceType type = SourceFinder.getSource(query);
+ String identifier = SourceFinder.getIdentifier(query);
+
+ if (identifier != null) {
+ TrackLoadingHandler loadingHandler = new TrackLoadingHandler(context, this, identifier, type, silent);
+
+ playerManager.loadItemOrdered(this, identifier, loadingHandler);
+ }
+ }
+
+ public synchronized void stopTrack() {
+ player.stopTrack();
+ }
+
+ public synchronized CommandExecutionResult voteSkip(CommandContext context) {
+ VoiceChannel voiceChannel = (VoiceChannel) guild.getAudioManager().getConnectedChannel();
+
+ if (voiceChannel == null) {
+ return CommandExecutionResult.none();
+ }
+
+ if (!isCurrentlyPlaying()) {
+ return CommandExecutionResult.none();
+ }
+
+ QueueManager.TrackInfo info = scheduler.getQueueManager().peekHeadOfQueue();
+ if (info == null) {
+ return CommandExecutionResult.none();
+ }
+
+ // Minus the bot itself
+ int connected = voiceChannel.getMembers().size() - 1;
+
+ if (connected < 3) {
+ context.reply("Skipping current track since not enough people are connected to the voice channel.");
+ scheduler.getQueueManager().skip();
+
+ return CommandExecutionResult.none();
+ }
+
+ double needed = connected / 2.1;
+
+ info.addSkip(context.getGuildUser());
+ logger.debug("VoteSkip: guild={}, connected={}, needed={}, skips={}, user={}", context.getGuild().getId(), connected, needed, info.getSkips().size(), context.getGuildUser().getId());
+
+ if (info.getSkips().size() > needed) {
+ return scheduler.getQueueManager().skip();
+ } else {
+ context.reply("Adding your vote to skip the current track. Current votes: %d/%d", info.getSkips().size(), (int) Math.ceil(needed));
+ }
+
+ return CommandExecutionResult.none();
+ }
+
+ public synchronized void setVolume(int volume) {
+ player.setVolume(volume);
+ }
+
+ public int getQueueSize() {
+ return scheduler.getQueueManager().getQueueSize();
+ }
+
+ public TrackScheduler getScheduler() {
+ return scheduler;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/QueueManager.java b/src/main/java/me/bobert/netneutrality/music/QueueManager.java
new file mode 100644
index 0000000..9676928
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/QueueManager.java
@@ -0,0 +1,315 @@
+package me.bobert.netneutrality.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import me.bobert.netneutrality.command.CommandExecutionResult;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.function.Predicate;
+
+public class QueueManager {
+
+ public static class TrackInfo {
+
+ private final AudioTrack track;
+ private final Member member;
+ private final Set skips;
+ private final Logger logger = LogManager.getLogger(QueueManager.class);
+
+ public TrackInfo(AudioTrack track, Member member) {
+ this.track = track;
+ this.member = member;
+ this.skips = new HashSet<>();
+ }
+
+ public AudioTrack getTrack() {
+ return track;
+ }
+
+ public Set getSkips() {
+ return skips;
+ }
+
+ public void addSkip(Member member) {
+ if (!hasSkip(member)) {
+ logger.debug("VoteSkip: user={}, skip doesn't exist, before={}, after={}", member.getId(), skips.size(), skips.size() + 1);
+ skips.add(member.getId());
+ }
+ }
+
+ public boolean hasSkip(Member member) {
+ return skips.contains(member.getId());
+ }
+
+ public Member getRequester() {
+ return member;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TrackInfo trackInfo = (TrackInfo) o;
+ return track.equals(trackInfo.track);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(track);
+ }
+
+ @Override
+ public String toString() {
+ return "TrackInfo{" +
+ "track=" + track.getInfo().title +
+ ", member=" + member.getEffectiveName() +
+ ", skips=" + skips +
+ '}';
+ }
+ }
+
+ private final Logger logger = LogManager.getLogger(QueueManager.class);
+
+ private final TrackScheduler scheduler;
+ private final AudioPlayer player;
+ private final Guild guild;
+ private final Deque queue;
+
+ public QueueManager(TrackScheduler scheduler) {
+ this.scheduler = scheduler;
+ this.player = scheduler.getPlayer();
+ this.guild = scheduler.getGuild();
+
+ this.queue = new LinkedBlockingDeque<>();
+ }
+
+ public List getQueue() {
+ synchronized (queue) {
+ return queue.stream().toList();
+ }
+ }
+
+ public TrackScheduler getScheduler() {
+ return scheduler;
+ }
+
+ public Guild getGuild() {
+ return guild;
+ }
+
+ public int getQueueSize() {
+ synchronized (queue) {
+ return queue.size();
+ }
+ }
+
+ public List getQueueWithFilter(Predicate super TrackInfo> filter) {
+ synchronized (queue) {
+ return queue.stream().filter(filter).toList();
+ }
+ }
+
+ public TrackInfo peekHeadOfQueue() {
+ synchronized (queue) {
+ return queue.peek();
+ }
+ }
+
+ public void removeHeadOfQueue() {
+ synchronized (queue) {
+ queue.poll();
+ }
+ }
+
+ public boolean isEmpty() {
+ synchronized (queue) {
+ return queue.isEmpty();
+ }
+ }
+
+ public CommandExecutionResult clearQueue() {
+ scheduler.getMusicManager().leave();
+
+ if (queue.isEmpty()) {
+ return CommandExecutionResult.warn("Queue is empty!");
+ }
+
+ synchronized (queue) {
+ int queueSize = queue.size();
+ if (queueSize == 0) {
+ return CommandExecutionResult.warn("Queue is empty!");
+ }
+
+ queue.clear();
+ return CommandExecutionResult.success(String.format("Removed %d track%s from the queue", queueSize--, (queueSize > 1 ? "s" : "")));
+ }
+ }
+
+ public void removeTracksFromUser(Member member) {
+ synchronized (queue) {
+ int size = queue.stream().filter(trackInfo -> member.equals(trackInfo.getRequester())).toList().size();
+
+ if (size > 0) {
+ if (size == queue.size()) {
+ clearQueue();
+ return;
+ }
+
+ TrackInfo first = queue.peek();
+ queue.removeIf(trackInfo -> member.equals(trackInfo.getRequester()));
+
+ if (first != null && member.equals(first.getRequester())) {
+ skip(1);
+ }
+ }
+ }
+ }
+
+ public ArrayList getAllQueuedTrack() {
+ synchronized (queue) {
+ ArrayList tracks = new ArrayList<>();
+
+ queue.forEach(trackInfo -> tracks.add(trackInfo.getTrack()));
+ return tracks;
+ }
+ }
+
+ public void queue(AudioTrack track, Member member) {
+ TrackInfo info = new TrackInfo(track, member);
+
+ synchronized (queue) {
+ if (queue.contains(info)) {
+ return;
+ }
+
+ if (player.getPlayingTrack() == null && !queue.isEmpty()) {
+ queue.addFirst(info);
+ }
+
+ if ((player.getPlayingTrack() == null && queue.isEmpty()) || player.getPlayingTrack() != null) {
+ queue.add(info);
+ }
+
+ if (player.getPlayingTrack() == null) {
+ AudioTrack toPlay = (queue.peek() == null ? null : queue.peek().getTrack());
+ player.startTrack(toPlay, true);
+ }
+ }
+ }
+
+ public void startNextTrack() {
+ synchronized (queue) {
+ TrackInfo last = queue.poll();
+ TrackInfo next = queue.peek();
+
+ if (next != null) {
+ player.startTrack(next.getTrack().makeClone(), false);
+ } else {
+ player.stopTrack();
+ }
+ }
+ }
+
+ public CommandExecutionResult skip() {
+ return skip(1);
+ }
+
+ public CommandExecutionResult skip(int nbTrack) {
+ if (nbTrack == 0) {
+ nbTrack = 1;
+ }
+
+ synchronized (queue) {
+ if (queue.isEmpty()) {
+ scheduler.getMusicManager().leave();
+ return CommandExecutionResult.info("Queue is empty, leaving voice channel");
+ }
+
+ if (nbTrack > queue.size()) {
+ nbTrack = 1;
+ }
+
+ logger.debug("Guild {}: skipping {} tracks", guild.getId(), nbTrack);
+ for (int i = 1; i < nbTrack; i++) {
+ queue.poll();
+ }
+
+ if (queue.isEmpty()) {
+ scheduler.getMusicManager().leave();
+ return CommandExecutionResult.info("Queue is empty, leaving voice channel");
+ }
+
+ startNextTrack();
+
+ return CommandExecutionResult.success(String.format("Skipping %s track%s..", nbTrack, (nbTrack > 1 ? "s" : "")));
+ }
+ }
+
+ public CommandExecutionResult shuffle() {
+ if (queue.size() > 3) {
+ return CommandExecutionResult.none();
+ }
+
+ synchronized (queue) {
+ if (queue.size() > 3) {
+ logger.debug("Guild {}: shuffling queue", guild.getId());
+ TrackInfo current = queue.poll();
+
+ ArrayList newQueue = new ArrayList<>(queue);
+
+ Collections.shuffle(newQueue);
+
+ queue.clear();
+ queue.add(current);
+ queue.addAll(newQueue);
+
+ return CommandExecutionResult.success("Queue is now shuffled.");
+ }
+
+ return CommandExecutionResult.none();
+ }
+ }
+
+ public CommandExecutionResult removeFromQueue(int index) {
+ if (queue.isEmpty() || index < 1) {
+ return CommandExecutionResult.none();
+ }
+
+ synchronized (queue) {
+ if (queue.isEmpty()) {
+ return CommandExecutionResult.none();
+ }
+
+ if (index > queue.size()) {
+ return CommandExecutionResult.error("Position is out of bounds");
+ }
+
+ logger.debug("Guild {}: removing index {} from queue", guild.getId(), index - 1);
+
+ if (index > 1) {
+ List newQueue = new ArrayList<>(queue);
+ newQueue.remove(index - 1);
+
+ queue.clear();
+ queue.addAll(newQueue);
+
+ return CommandExecutionResult.success(String.format("Removed track #%d from the queue", index));
+ }
+
+ skip();
+ return CommandExecutionResult.none();
+
+ }
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/SourceFinder.java b/src/main/java/me/bobert/netneutrality/music/SourceFinder.java
new file mode 100644
index 0000000..b42bc6f
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/SourceFinder.java
@@ -0,0 +1,72 @@
+package me.bobert.netneutrality.music;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SourceFinder {
+
+ public enum AudioSourceType {
+ Youtube,
+ YTMusic,
+ Soundcloud,
+ Bandcamp,
+ Vimeo,
+ YTSearch,
+ Udio,
+ DirectLinkMp3,
+ Nothing
+
+ }
+
+ private static final Pattern youtubePattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
+ private static final Pattern youtubeMusicPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?music\\.(?:youtube\\.com|youtu\\.be)/.*?(?:embed|e|v|watch.*?v=)?/?([-_a-zA-Z0-9]{10,})?(?:&?index=\\d+)?(?>(?:playlist\\?|&)?list=([^#\\\\&?]{12,}))?", Pattern.CASE_INSENSITIVE);
+ private static final Pattern soundcloudPattern = Pattern.compile("^(?:https?://)?(?:www\\.)?(?:m\\.)?soundcloud\\.com/([a-zA-Z0-9-_]+)/([a-zA-Z0-9-_]+)(?:\\\\?.*|)$", Pattern.CASE_INSENSITIVE);
+ private static final Pattern udioPattern = Pattern.compile("^https://www.udio.com/songs/[0-9A-Za-z]+$", Pattern.CASE_INSENSITIVE);
+ private static final Pattern mp3Pattern = Pattern.compile("^https?://(?:.*)(?:.mp3)$", Pattern.CASE_INSENSITIVE);
+
+ public static AudioSourceType getSource(String url) {
+ Matcher youtubeMatcher = youtubePattern.matcher(url);
+ Matcher youtubeMusicMatcher = youtubeMusicPattern.matcher(url);
+ Matcher soundcloudMatcher = soundcloudPattern.matcher(url);
+ Matcher udioMatcher = udioPattern.matcher(url);
+ Matcher mp3Matcher = mp3Pattern.matcher(url);
+
+ if (youtubeMatcher.find())
+ return AudioSourceType.Youtube;
+
+ if (youtubeMusicMatcher.find())
+ return AudioSourceType.YTMusic;
+
+ if (soundcloudMatcher.find())
+ return AudioSourceType.Soundcloud;
+
+ if (udioMatcher.find())
+ return AudioSourceType.Udio;
+
+ if (mp3Matcher.find())
+ return AudioSourceType.DirectLinkMp3;
+
+ if (!(url.startsWith("http://") || url.startsWith("https://")))
+ return AudioSourceType.YTSearch;
+
+ return AudioSourceType.Nothing;
+ }
+
+ public static String getIdentifier(String url) {
+ String identifier;
+
+ identifier = switch (getSource(url)) {
+ case Youtube, YTMusic, Soundcloud, Udio, DirectLinkMp3 -> url;
+ case YTSearch -> "ytsearch:" + url;
+ default -> null;
+ };
+
+ return identifier;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java
new file mode 100644
index 0000000..007a040
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/TrackLoadingHandler.java
@@ -0,0 +1,92 @@
+package me.bobert.netneutrality.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+
+import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.utils.MessageFunction;
+import net.dv8tion.jda.api.entities.Guild;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class TrackLoadingHandler implements AudioLoadResultHandler {
+ private final Logger logger = LogManager.getLogger(TrackLoadingHandler.class);
+
+ private final String identifier;
+ private final SourceFinder.AudioSourceType type;
+ private final boolean silent;
+ private final Guild guild;
+ private final TrackScheduler scheduler;
+ private final CommandContext context;
+
+ public TrackLoadingHandler(CommandContext context, MusicManager manager, String identifier, SourceFinder.AudioSourceType type, boolean silent) {
+ this.identifier = identifier;
+ this.type = type;
+ this.silent = silent;
+ this.guild = manager.getGuild();
+ this.scheduler = manager.getScheduler();
+ this.context = context;
+ }
+
+ @Override
+ public void trackLoaded(AudioTrack audioTrack) {
+ context.deleteMessage();
+
+ logger.debug("Guild {}: track loaded: {}", guild.getId(), audioTrack.getIdentifier());
+ scheduler.getQueueManager().queue(audioTrack, context.getGuildUser());
+
+ if (!silent) {
+ context.reply(MessageFunction.buildAddingTrackMessage(audioTrack, type));
+ }
+ }
+
+ @Override
+ public void playlistLoaded(AudioPlaylist playlist) {
+ logger.debug("Guild {}: playlist loaded: {}", guild.getId(), identifier);
+
+ if (!playlist.getTracks().isEmpty()) {
+ if (playlist.isSearchResult()) {
+
+ AudioTrack track = playlist.getTracks().get(0);
+ scheduler.getQueueManager().queue(track, context.getGuildUser());
+
+ if (!silent) {
+ context.reply(MessageFunction.buildAddingTrackMessage(track, type));
+ }
+
+ } else {
+ context.deleteMessage();
+ if (!silent) {
+ context.reply(MessageFunction.buildAddingPlaylistMessage(playlist, identifier, type));
+ }
+
+ for (AudioTrack track : playlist.getTracks()) {
+ scheduler.getQueueManager().queue(track, context.getGuildUser());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void noMatches() {
+ if (!silent)
+ context.replyToMessage("No matches found");
+ }
+
+ @Override
+ public void loadFailed(FriendlyException e) {
+ logger.debug("Guild {}: load failed for identifier {}", guild.getId(), identifier, e);
+ LogHandler.log(e);
+
+ if (!silent)
+ context.replyToMessage(MessageFunction.buildSentryErrorMessage(new RuntimeException("Failed to load track: " + e.getMessage())));
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java b/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java
new file mode 100644
index 0000000..99de059
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/TrackScheduler.java
@@ -0,0 +1,130 @@
+package me.bobert.netneutrality.music;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
+import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
+import me.bobert.netneutrality.core.LogHandler;
+import me.bobert.netneutrality.utils.MessageFunction;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class TrackScheduler extends AudioEventAdapter {
+ private final static Logger logger = LogManager.getLogger(TrackScheduler.class);
+
+ private final QueueManager queueManager;
+ private final MusicManager manager;
+ private final AudioPlayer player;
+ private MessageChannel currentChannel;
+ private final Guild guild;
+
+ public TrackScheduler(MusicManager manager) {
+ this.manager = manager;
+ this.player = manager.getPlayer();
+ this.guild = manager.getGuild();
+ this.queueManager = new QueueManager(this);
+ }
+
+ public QueueManager getQueueManager() {
+ return queueManager;
+ }
+
+ public MusicManager getMusicManager() {
+ return manager;
+ }
+
+ public AudioPlayer getPlayer() {
+ return player;
+ }
+
+ public Guild getGuild() {
+ return guild;
+ }
+
+ public MessageChannel getTextChannel() {
+ return currentChannel;
+ }
+
+ public synchronized void attachTextChannel(MessageChannel channel) {
+ this.currentChannel = channel;
+ }
+
+ public synchronized void detachTextChannel() {
+ this.currentChannel = null;
+ }
+
+ public void sendMessageToChannel(String msg) {
+ if (currentChannel != null && currentChannel.canTalk()) {
+ currentChannel.sendMessage(msg).queue();
+ }
+ }
+
+ public void sendMessageToChannel(MessageEmbed embed, MessageEmbed... embeds) {
+ if (currentChannel != null && currentChannel.canTalk()) {
+ currentChannel.sendMessageEmbeds(embed, embeds).queue();
+ }
+ }
+
+ @Override
+ public void onPlayerPause(AudioPlayer player) {
+ // Player was paused
+ }
+
+ @Override
+ public void onPlayerResume(AudioPlayer player) {
+ // Player was resumed
+ }
+
+ @Override
+ public void onTrackStart(AudioPlayer player, AudioTrack track) {
+ logger.debug("Guild {}: starting track: {}", guild.getId(), track.getIdentifier());
+ }
+
+ @Override
+ public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
+ logger.debug("Guild {}: currently playing track ended: {}", guild.getId(), track.getIdentifier());
+
+ if (endReason == AudioTrackEndReason.LOAD_FAILED) {
+ logger.debug("Guild {}: failed loading track: {}", guild.getId(), track.getIdentifier());
+ }
+
+ // check if mayStartNext
+ if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.LOAD_FAILED) {
+ queueManager.startNextTrack();
+ }
+
+ if (endReason == AudioTrackEndReason.STOPPED) {
+ queueManager.removeHeadOfQueue();
+ }
+
+ // endReason == FINISHED: A track finished or died by an exception (mayStartNext = true).
+ // endReason == LOAD_FAILED: Loading of a track failed (mayStartNext = true).
+ // endReason == STOPPED: The player was stopped.
+ // endReason == REPLACED: Another track started playing while this had not finished
+ // endReason == CLEANUP: Player hasn't been queried for a while, if you want you can put a
+ // clone of this back to your queue
+ }
+
+ @Override
+ public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) {
+ logger.debug("Guild {}: exception while playing track [{}]: {}", guild.getId(), track.getIdentifier(), exception.getMessage());
+ LogHandler.log(exception);
+ sendMessageToChannel(MessageFunction.buildSentryErrorMessage(new RuntimeException(String.format("Error while playing track [%s]: %s", track.getInfo().title, exception.getMessage()))));
+ }
+
+ @Override
+ public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) {
+ logger.debug("Guild {}: currently playing track is stuck: {}", guild.getId(), track.getIdentifier());
+ sendMessageToChannel("Current track is stuck, skipping to next");
+ queueManager.startNextTrack();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java
new file mode 100644
index 0000000..eaeaa79
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioSourceManager.java
@@ -0,0 +1,160 @@
+package me.bobert.netneutrality.music.source;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
+import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager;
+import com.sedmelluq.discord.lavaplayer.tools.DataFormatTools;
+import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
+import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
+import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
+import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterfaceManager;
+import com.sedmelluq.discord.lavaplayer.track.AudioItem;
+import com.sedmelluq.discord.lavaplayer.track.AudioReference;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;
+
+public class UdioAudioSourceManager implements AudioSourceManager {
+
+ private static final Logger log = LogManager.getLogger(UdioAudioSourceManager.class);
+
+
+ private static final String TRACK_URL_REGEX = "^https://www.udio.com/songs/[0-9A-Za-z]+$";
+ private static final Pattern trackUrlPattern = Pattern.compile(TRACK_URL_REGEX);
+
+ private final HttpInterfaceManager httpInterfaceManager;
+
+ public UdioAudioSourceManager() {
+ httpInterfaceManager = HttpClientTools.createDefaultThreadLocalManager();
+ }
+
+ @Override
+ public String getSourceName() {
+ return "udio";
+ }
+
+ @Override
+ public AudioItem loadItem(AudioPlayerManager manager, AudioReference reference) {
+ if (!trackUrlPattern.matcher(reference.identifier).matches()) {
+ return null;
+ }
+
+ try (HttpInterface httpInterface = httpInterfaceManager.getInterface()) {
+ return loadFromTrackPage(httpInterface, reference.identifier);
+ } catch (IOException e) {
+ throw new FriendlyException("Loading Udio track information failed.", SUSPICIOUS, e);
+ }
+
+ }
+
+
+
+ @Override
+ public boolean isTrackEncodable(AudioTrack track) {
+ return true;
+ }
+
+ @Override
+ public void encodeTrack(AudioTrack track, DataOutput output) {
+
+ }
+
+ @Override
+ public AudioTrack decodeTrack(AudioTrackInfo trackInfo, DataInput input) {
+ return new UdioAudioTrack(trackInfo, this);
+ }
+
+ @Override
+ public void shutdown() {
+ ExceptionTools.closeWithWarnings(httpInterfaceManager);
+ }
+
+ public HttpInterface getHttpInterface() {
+ return httpInterfaceManager.getInterface();
+ }
+
+ private AudioItem loadFromTrackPage(HttpInterface httpInterface, String trackUrl) throws IOException {
+
+ try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackUrl))) {
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ if (statusCode == HttpStatus.SC_NOT_FOUND) {
+ return AudioReference.NO_TRACK;
+ } else if (!HttpClientTools.isSuccessWithContent(statusCode)) {
+ throw new FriendlyException("Server responded with an error.", SUSPICIOUS,
+ new IllegalStateException("Response code is " + statusCode));
+ }
+
+ return loadTrackFromPageContent(trackUrl, IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
+ }
+ }
+
+ private AudioTrack loadTrackFromPageContent(String trackUrl, String content) throws IOException {
+ JsonBrowser trackInfo = loadTrackInfoFromPageContent(content);
+
+ if (trackInfo == null) {
+ log.debug("Udio HTML: {}", content);
+ throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null);
+ }
+
+ String title = trackInfo.get("title").text();
+ String artist = trackInfo.get("artist").text();
+ long duration = (long) (trackInfo.get("duration").as(Double.class) * 1000.0);
+ String playbackUrl = trackInfo.get("song_path").text();
+ String artworkUrl = getArtworkUrlFromImagePath(trackInfo);
+
+ AudioTrackInfo audioTrackInfo = new AudioTrackInfo(title, artist, duration, trackUrl, false, playbackUrl, artworkUrl, null);
+
+ return new UdioAudioTrack(audioTrackInfo, this);
+ }
+
+ protected JsonBrowser loadTrackInfoFromPageContent(String content) throws IOException {
+ String startTag = "self.__next_f.push([1,\"9:";
+ String endTag = "\\n\"])";
+
+ String json = DataFormatTools.extractBetween(content, startTag, endTag);
+
+ if (json == null) {
+ log.error("Track JSON not found in page");
+ return null;
+ }
+
+ try {
+ String unescapedJson = json.replace("\\", "");
+ JsonBrowser parsedJson = JsonBrowser.parse(unescapedJson);
+
+ return parsedJson.index(3).get("children").index(3).get("children").index(3).get("track");
+ } catch (JsonParseException e) {
+ log.error("Failed to parse track JSON: {}", json);
+ return null;
+ }
+ }
+
+ private String getArtworkUrlFromImagePath(JsonBrowser trackInfo) {
+ String imagePath = trackInfo.get("image_path").text();
+ String artworkOptions = "width=3840,quality=75";
+
+ return imagePath.replace("public", artworkOptions);
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/music/source/UdioAudioTrack.java b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioTrack.java
new file mode 100644
index 0000000..7b90151
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/music/source/UdioAudioTrack.java
@@ -0,0 +1,80 @@
+package me.bobert.netneutrality.music.source;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.container.mp3.Mp3AudioTrack;
+import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
+import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
+import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
+import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
+import com.sedmelluq.discord.lavaplayer.tools.io.PersistentHttpStream;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
+import com.sedmelluq.discord.lavaplayer.track.DelegatedAudioTrack;
+import com.sedmelluq.discord.lavaplayer.track.playback.LocalAudioTrackExecutor;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;
+
+public class UdioAudioTrack extends DelegatedAudioTrack {
+
+ private static final Logger log = LogManager.getLogger(UdioAudioTrack.class);
+
+ private final UdioAudioSourceManager sourceManager;
+
+ /**
+ * @param trackInfo Track info
+ * @param sourceManager Source manager which was used to find this track
+ */
+ public UdioAudioTrack(AudioTrackInfo trackInfo, UdioAudioSourceManager sourceManager) {
+ super(trackInfo);
+
+ this.sourceManager = sourceManager;
+ }
+
+ @Override
+ public void process(LocalAudioTrackExecutor localExecutor) throws Exception {
+ try (HttpInterface httpInterface = sourceManager.getHttpInterface()) {
+ String playbackUrl = loadPlaybackUrl(httpInterface);
+
+ log.debug("Starting Udio track from URL: {}", playbackUrl);
+
+ try (PersistentHttpStream stream = new PersistentHttpStream(httpInterface, new URI(playbackUrl), null)) {
+ processDelegate(new Mp3AudioTrack(trackInfo, stream), localExecutor);
+ }
+ }
+ }
+
+ private String loadPlaybackUrl(HttpInterface httpInterface) throws IOException {
+ JsonBrowser trackInfo = loadTrackInfo(httpInterface);
+
+ if (trackInfo == null) {
+ throw new FriendlyException("Udio: Failed to extract track information from web page.", SUSPICIOUS, null);
+ }
+
+ return trackInfo.get("song_path").text();
+ }
+
+ private JsonBrowser loadTrackInfo(HttpInterface httpInterface) throws IOException {
+ try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(trackInfo.identifier))) {
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ if (!HttpClientTools.isSuccessWithContent(statusCode)) {
+ throw new FriendlyException("Server responded with an error.", SUSPICIOUS,
+ new IllegalStateException("Response code for player config is " + statusCode));
+ }
+
+ return sourceManager.loadTrackInfoFromPageContent(IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java b/src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java
new file mode 100644
index 0000000..1c33201
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/thread/BaseThreadFactory.java
@@ -0,0 +1,31 @@
+package me.bobert.netneutrality.thread;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.exceptions.ExecutorException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class BaseThreadFactory implements ThreadFactory {
+
+ private final AtomicLong threadId = new AtomicLong(1);
+ private final String name;
+
+ public BaseThreadFactory(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Thread newThread(@NotNull Runnable r) {
+ Thread thread = new Thread(r);
+ thread.setName(name + "-" + threadId.getAndIncrement());
+ thread.setUncaughtExceptionHandler(new ExecutorException());
+ return thread;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java
new file mode 100644
index 0000000..e97e091
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/thread/MusicPlayerAutoDisconnectThread.java
@@ -0,0 +1,55 @@
+package me.bobert.netneutrality.thread;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.NetNeutrality;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.List;
+
+public class MusicPlayerAutoDisconnectThread implements Runnable {
+
+ private final Logger logger = LogManager.getLogger(MusicPlayerAutoDisconnectThread.class);
+ private final MusicManager manager;
+
+ //TODO: move to scheduler
+ public MusicPlayerAutoDisconnectThread(MusicManager musicManager) {
+ manager = musicManager;
+ }
+
+ @Override
+ public void run() {
+ Guild guild = manager.getGuild();
+ VoiceChannel channel = (VoiceChannel) guild.getAudioManager().getConnectedChannel();
+
+ try {
+ if (channel != null) {
+ if (conditionsMetToDisconnect(channel)) {
+ // Sleep 30 seconds then check again (prevents disconnecting right after a track ends)
+ Thread.sleep(30000);
+
+ if (conditionsMetToDisconnect(channel)) {
+ logger.debug("Guild {}: auto-disconnect player from voice channel: {}", guild.getId(), channel.getId());
+ manager.leave();
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean conditionsMetToDisconnect(VoiceChannel channel) {
+ List members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList();
+
+ return members.isEmpty() || manager.getScheduler().getQueueManager().isEmpty();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/thread/ShutdownThread.java b/src/main/java/me/bobert/netneutrality/thread/ShutdownThread.java
new file mode 100644
index 0000000..c9900e0
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/thread/ShutdownThread.java
@@ -0,0 +1,28 @@
+package me.bobert.netneutrality.thread;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.NetNeutrality;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+
+public class ShutdownThread implements Runnable {
+
+ @Override
+ public void run() {
+ if (LogManager.getContext() instanceof LoggerContext) {
+ System.out.println("Shutting down log4j2");
+ Configurator.shutdown((LoggerContext) LogManager.getContext());
+ }
+
+ if (NetNeutrality.getJDA() != null) {
+ NetNeutrality.shutdown();
+ }
+
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/thread/ThreadManager.java b/src/main/java/me/bobert/netneutrality/thread/ThreadManager.java
new file mode 100644
index 0000000..f2d88e7
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/thread/ThreadManager.java
@@ -0,0 +1,75 @@
+package me.bobert.netneutrality.thread;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import java.util.Map;
+import java.util.concurrent.*;
+
+public class ThreadManager {
+
+ public enum ExecutorType {
+ SINGLE, SCHEDULED, FIXED
+ }
+
+ private static final Map executors = new ConcurrentHashMap<>();
+
+ public static ScheduledExecutorService getMusicExecutor() {
+ return (ScheduledExecutorService) executors.computeIfAbsent(
+ "music-executor",
+ s -> Executors.newScheduledThreadPool(
+ Runtime.getRuntime().availableProcessors(),
+ new BaseThreadFactory("music-executor")
+ )
+ );
+ }
+
+ public static ExecutorService getExecutor() {
+ return executors.computeIfAbsent("general-executor", s -> Executors.newCachedThreadPool(new BaseThreadFactory("general-executor")));
+ }
+
+ public static ExecutorService getExecutor(String name) {
+ return executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name)));
+ }
+
+ public static ExecutorService getExecutor(String name, ExecutorType type) {
+ ExecutorService executor;
+
+ switch (type) {
+ case FIXED -> executor = executors.computeIfAbsent(name, s -> Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name)));
+ case SCHEDULED -> executor = executors.computeIfAbsent(name, s -> Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new BaseThreadFactory(name)));
+ default -> executor = executors.computeIfAbsent(name, s -> Executors.newSingleThreadExecutor(new BaseThreadFactory(name)));
+ }
+
+ return executor;
+ }
+
+ public static void shutdownExecutor() {
+ System.out.println("[Executor] Shutting down...");
+
+ try {
+ for (ExecutorService ex : executors.values()) {
+ ex.shutdown();
+ ex.awaitTermination(1, TimeUnit.SECONDS);
+ }
+ }
+ catch (InterruptedException e) {
+ System.err.println("[Executor] Running tasks interrupted");
+ }
+ finally {
+ executors.forEach((name, ex) -> {
+ if (!ex.isTerminated()) {
+ System.err.println("[Executor: " + name + "] Forcing shutdown of running tasks..");
+ ex.shutdownNow();
+ }
+ });
+
+ }
+
+ System.out.println("[Executor] Shutdown finished!");
+ }
+
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java b/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java
new file mode 100644
index 0000000..95ae067
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/ChannelMessagePurger.java
@@ -0,0 +1,170 @@
+package me.bobert.netneutrality.utils;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.ISpan;
+import io.sentry.ITransaction;
+import io.sentry.Sentry;
+import io.sentry.SpanStatus;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.thread.ThreadManager;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.MessageHistory;
+import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ChannelMessagePurger {
+
+ private static final Logger logger = LogManager.getLogger(ChannelMessagePurger.class);
+ private static final Map instances = new HashMap<>();
+ private final GuildMessageChannel channel;
+ private final Lock lock;
+
+ private ChannelMessagePurger(GuildMessageChannel channel) {
+ this.channel = channel;
+ this.lock = new ReentrantLock();
+ }
+
+ public static ChannelMessagePurger getPurger(GuildMessageChannel channel) {
+ return instances.computeIfAbsent(channel, ChannelMessagePurger::new);
+ }
+
+ public boolean canLockBeAcquired() {
+ boolean acquired = lock.tryLock();
+ if (acquired)
+ lock.unlock();
+
+ return acquired;
+ }
+
+ public void purgeMessages(CommandContext context, int toPurge) {
+ ThreadManager.getExecutor().submit(() -> {
+ if (lock.tryLock()) {
+ try {
+ int total = countAllChannelMessages(channel);
+ int finalToPurge = toPurge;
+
+ if (finalToPurge > total) {
+ finalToPurge = total;
+ }
+
+ safePurge(context, finalToPurge);
+ } finally {
+ lock.unlock();
+ }
+ }
+ });
+ }
+
+ public void purgeChannel(CommandContext context) {
+ ThreadManager.getExecutor().submit(() -> {
+ if (lock.tryLock()) {
+ try {
+ int total = countAllChannelMessages(channel);
+ context.reply("Number of messages to purge: %d", total);
+
+ safePurge(context, total);
+ } finally {
+ lock.unlock();
+ }
+ }
+ });
+ }
+
+ private synchronized int countAllChannelMessages(GuildMessageChannel channel) {
+ logger.info("Guild {}: counting all messages from channel {}", channel.getGuild().getId(), channel.getId());
+ MessageHistory history = channel.getHistory();
+ int totalCount = 0;
+ int iterationCount = 0;
+
+ while (totalCount == iterationCount) {
+ history.retrievePast(100).complete();
+
+ iterationCount += 100;
+ totalCount = history.size();
+ }
+ logger.info("Guild {}: Counted {} messages from channel {}", channel.getGuild().getId(), totalCount, channel.getId());
+
+ return totalCount;
+ }
+
+ private synchronized void safePurge(CommandContext context, int toPurge) {
+ ITransaction transaction = Sentry.startTransaction(String.format("purge messages from guild %s)", channel.getGuild().getId()), "task");
+ transaction.setData("channel", channel.getId());
+ transaction.setData("toPurge", toPurge);
+
+ if (toPurge == 1) {
+ toPurge += 1;
+ }
+
+ int tracker = toPurge;
+ int iteration = tracker / 100;
+ boolean olderThan2Weeks = false;
+ context.replyToMessage("Purging iterations to do: %d", iteration);
+
+ try {
+ for (int i = 0; i < iteration; i++) {
+ ISpan innerSpan = transaction.startChild("retrieve-messages", "Processing iteration " + (i + 1) + "/" + iteration);
+ try {
+ List messages = channel.getHistory().retrievePast(100).complete();
+ channel.deleteMessages(messages).complete();
+ tracker -= 100;
+ innerSpan.setStatus(SpanStatus.OK);
+ } catch (IllegalArgumentException ex) {
+ olderThan2Weeks = true;
+ } catch (Exception e) {
+ innerSpan.setThrowable(e);
+ innerSpan.setStatus(SpanStatus.INTERNAL_ERROR);
+ } finally {
+ innerSpan.finish();
+ }
+ }
+
+ if (tracker > 0) {
+ ISpan innerSpan = transaction.startChild("retrieve-messages", String.format("Processing final iteration (%d messages)", tracker));
+ try {
+ List messages = channel.getHistory().retrievePast(tracker).complete();
+ channel.deleteMessages(messages).complete();
+ innerSpan.setStatus(SpanStatus.OK);
+ } catch (IllegalArgumentException ex) {
+ olderThan2Weeks = true;
+ } catch (Exception e) {
+ innerSpan.setThrowable(e);
+ innerSpan.setStatus(SpanStatus.INTERNAL_ERROR);
+ } finally {
+ innerSpan.finish();
+ }
+
+
+ innerSpan.finish();
+ }
+
+ } catch (Exception e) {
+ transaction.setThrowable(e);
+ transaction.setStatus(SpanStatus.INTERNAL_ERROR);
+ } finally {
+ transaction.finish();
+ MessageCreateBuilder builder = new MessageCreateBuilder()
+ .addContent("Finished purging ")
+ .addContent(String.valueOf(toPurge))
+ .addContent(" messages.");
+
+ if (olderThan2Weeks) {
+ builder.addContent("\n").addContent("Messages older than two weeks weren't deleted.");
+ }
+ context.reply(builder.build());
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java
new file mode 100644
index 0000000..2996950
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/JpaUtils.java
@@ -0,0 +1,86 @@
+package me.bobert.netneutrality.utils;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import io.sentry.Breadcrumb;
+import io.sentry.Sentry;
+import io.sentry.SentryLevel;
+import me.bobert.netneutrality.core.GuildManager;
+import me.bobert.netneutrality.core.SessionManager;
+import me.bobert.netneutrality.db.entities.OGuild;
+import org.hibernate.HibernateException;
+import org.hibernate.ObjectNotFoundException;
+import org.hibernate.Session;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+public class JpaUtils {
+
+ /**
+ * Tries to read the persistent state into the object.
+ *
+ * If the object is not found in the database and is of type {@link OGuild}, tries to recreate entity and throws
+ * an exception to block execution.
+ *
+ * The session is not closed
+ *
+ * @param session The session in which to load the persistent object
+ * @param object The persistent object
+ * @param id Identifier
+ */
+ public static void tryLoad(Session session, Object object, Serializable id) {
+ try {
+ session.load(object, id);
+ } catch (HibernateException exception) {
+ if (exception instanceof ObjectNotFoundException) {
+ Sentry.addBreadcrumb(Breadcrumb.error("ObjectNotFoundException"));
+
+ if (object instanceof OGuild oGuild) {
+ String cachedId = oGuild.getGuildId();
+ Sentry.captureMessage("Trying to load Guild[" + cachedId + "] while entity doesn't exist in database, reloading..", SentryLevel.ERROR);
+
+ GuildManager guildManager = GuildManager.getManager(cachedId);
+ guildManager.reloadEntity();
+ guildManager.getBlacklistCache().clear();
+ Sentry.captureMessage("Guild[" + cachedId + "] reloaded", SentryLevel.INFO);
+ }
+ }
+
+ session.close();
+ throw exception;
+ }
+ }
+
+ public static void updateWithTransaction(Object persistantEntity, Runnable runnable) {
+ Session session = SessionManager.getSessionFactory().openSession();
+ session.beginTransaction();
+
+ runnable.run();
+
+ session.merge(persistantEntity);
+ session.getTransaction().commit();
+ session.close();
+ }
+
+ public static void getWithSession(OGuild oGuild, Consumer consumer) {
+ getWithSession(oGuild, oGuild.getGuildId(), consumer);
+ }
+
+ /**
+ * @param persistantEntity The entity to load
+ * @param id The identifier for the entity
+ * @param consumer The comsumer, which exposes the session
+ */
+ public static void getWithSession(Object persistantEntity, Serializable id, Consumer consumer) {
+ Session session = SessionManager.getSessionFactory().openSession();
+ tryLoad(session, persistantEntity, id);
+
+ consumer.accept(session);
+ session.close();
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java
new file mode 100644
index 0000000..3cd26b6
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/MessageFunction.java
@@ -0,0 +1,285 @@
+package me.bobert.netneutrality.utils;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
+import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
+import io.sentry.Sentry;
+import me.bobert.netneutrality.command.AbstractCommand;
+import me.bobert.netneutrality.command.CommandManager;
+import me.bobert.netneutrality.command.CommandType;
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.exceptions.CommandException;
+import me.bobert.netneutrality.music.MusicManager;
+import me.bobert.netneutrality.music.QueueManager;
+import me.bobert.netneutrality.music.SourceFinder;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import org.apache.commons.lang3.time.DurationFormatUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.awt.*;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+public class MessageFunction {
+
+ private final static Logger logger = LogManager.getLogger(MessageFunction.class);
+
+ public static MessageEmbed buildCurrentlyPlayingMessage(MusicManager manager) {
+ AudioTrack current = manager.getCurrentlyPlaying();
+
+ EmbedBuilder message = new EmbedBuilder();
+ message.setAuthor("Link to track", String.format("https://youtu.be/%s", current.getIdentifier()));
+ message.setThumbnail(String.format("https://img.youtube.com/vi/%s/default.jpg", current.getIdentifier()));
+ message.setTitle("Currently playing:");
+ message.setColor(Color.BLUE);
+ message.addField("Track title: ", current.getInfo().title, false);
+ message.addField("Duration: ", DurationFormatUtils.formatDuration(current.getPosition(), "**HH:mm:ss**", true) + " / " + DurationFormatUtils.formatDuration(current.getDuration(), "**HH:mm:ss**", true), false);
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildAddingTrackMessage(AudioTrack track, SourceFinder.AudioSourceType type) {
+ EmbedBuilder message = new EmbedBuilder();
+ String url = track.getIdentifier();
+ String thumbnail = null;
+
+ if (type == SourceFinder.AudioSourceType.Youtube || type == SourceFinder.AudioSourceType.YTSearch || type == SourceFinder.AudioSourceType.YTMusic) {
+ url = String.format("https://youtu.be/%s", track.getIdentifier());
+ thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", track.getIdentifier());
+ }
+
+ if (type == SourceFinder.AudioSourceType.Udio) {
+ thumbnail = track.getInfo().artworkUrl;
+ }
+
+ if (thumbnail != null) {
+ message.setThumbnail(thumbnail);
+ }
+ message.setTitle("Adding track to queue..");
+ message.setColor(Color.BLUE);
+ message.addField("Track title: ", String.format("[**`%s`**](%s)", track.getInfo().title, url), false);
+ message.addField("Duration: ", DurationFormatUtils.formatDuration(track.getDuration(), "**HH:mm:ss**", true), false);
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildSearchResultsMessage(List tracks) {
+ EmbedBuilder message = new EmbedBuilder();
+
+ message.setTitle("Select the track to add to the queue");
+ message.setColor(Color.BLUE);
+
+ int i = 0;
+ for (AudioTrack track : tracks) {
+ message.addField(++i + ": ", String.format("**`%s`**", track.getInfo().title), false);
+ }
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildAddingPlaylistMessage(AudioPlaylist playlist, String identifier, SourceFinder.AudioSourceType type) {
+ EmbedBuilder message = new EmbedBuilder();
+ String url = identifier;
+ String thumbnail = null;
+
+ if (type == SourceFinder.AudioSourceType.Youtube || type == SourceFinder.AudioSourceType.YTSearch) {
+ url = String.format("https://youtu.be/%s", playlist.getTracks().get(0).getIdentifier());
+ thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier());
+ }
+
+ if (type == SourceFinder.AudioSourceType.YTMusic) {
+ thumbnail = String.format("https://i3.ytimg.com/vi/%s/hqdefault.jpg", playlist.getTracks().get(0).getIdentifier());
+ }
+
+ if (thumbnail != null) {
+ message.setThumbnail(thumbnail);
+ }
+ message.setTitle("Adding playlist to queue..");
+ message.setColor(Color.BLUE);
+ message.addField("Playlist: ", String.format("[**`%s`**](%s)", playlist.getName(), url), false);
+ message.addField("Number of tracks: ", String.valueOf(playlist.getTracks().size()), false);
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildViewQueueMessage(MusicManager manager, boolean currentlyPlaying) {
+ ArrayList tracks = manager.getScheduler().getQueueManager().getAllQueuedTrack();
+ AudioTrack current = manager.getCurrentlyPlaying();
+ int numberofTracksShown = Math.min(tracks.size(), 10);
+
+ EmbedBuilder message = new EmbedBuilder();
+ message.setTitle(String.format("Current queue (showing %d out of %d items):", numberofTracksShown, tracks.size()));
+ message.setColor(Color.BLUE);
+
+ int index = 0;
+ for (AudioTrack track : tracks) {
+ AudioTrackInfo info = track.getInfo();
+
+ if (current != null && current.getInfo().title.equals(info.title) && currentlyPlaying) {
+ message.addField("Track #" + ++index + " (Currently playing)", info.title, false);
+ } else {
+ message.addField("Track #" + ++index, info.title, false);
+ }
+
+ if (index == 10) break;
+ }
+
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildUserTracksMessage(MusicManager manager, Member member) {
+ List tracks = manager.getScheduler()
+ .getQueueManager()
+ .getQueueWithFilter(trackInfo -> member.equals(trackInfo.getRequester()));
+ AudioTrack current = manager.getCurrentlyPlaying();
+ int numberofTracksShown = Math.min(tracks.size(), 10);
+
+ EmbedBuilder message = new EmbedBuilder();
+ message.setTitle(String.format("Tracks added by %s (showing %d out of %d items):", member.getAsMention(), numberofTracksShown, tracks.size()));
+ message.setColor(Color.BLUE);
+
+ int index = 0;
+ for (QueueManager.TrackInfo track : tracks) {
+ AudioTrackInfo info = track.getTrack().getInfo();
+
+ if (current != null && current.getInfo().title.equals(info.title)) {
+ message.addField("Track #" + ++index + " (Currently playing)", info.title, false);
+ } else {
+ message.addField("Track #" + ++index, info.title, false);
+ }
+
+ if (index == 10) break;
+ }
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static ArrayList buildHelpMessage(Member member) {
+ HashMap help = new HashMap<>();
+
+ for (AbstractCommand command : CommandManager.commands.values()) {
+ if (command.getType() == null)
+ continue;
+
+ String description = "No description yet";
+
+ if (command.isEnabled() && command.getType() != CommandType.UNLISTED) {
+ if (command.getType() == CommandType.ADMINISTRATION && !UserFunction.isAdmin(member))
+ continue;
+
+ if (command.getDescription() != null)
+ description = command.getDescription();
+
+ EmbedBuilder message;
+ if (help.containsKey(command.getType())) {
+ message = help.get(command.getType());
+ } else {
+ help.put(command.getType(), message = new EmbedBuilder());
+ }
+
+ if (message.isEmpty()) {
+ message.setTitle(command.getType().getDescription() + " commands for NetNeutrality");
+ message.setColor(Color.BLUE);
+ }
+
+ message.addField(String.format("%s", command.getCommandName()), description, false);
+ }
+ }
+
+ ArrayList messages = new ArrayList<>();
+ help.values().forEach(embedBuilder -> messages.add(embedBuilder.build()));
+
+ return messages;
+ }
+
+ public static MessageEmbed buildSentryErrorMessage(Exception ex) {
+ EmbedBuilder message = new EmbedBuilder();
+ message.setColor(Color.RED);
+ message.setTitle(":warning: Error during execution :warning:");
+ message.addField("Error message:", ex.getMessage(), false);
+ message.addField("Error ID:", Sentry.getLastEventId().toString(), false);
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildDebugMessage(CommandContext context, CommandException error) {
+ EmbedBuilder message = new EmbedBuilder();
+ message.setColor(Color.RED);
+ message.setTitle(":warning: Exception in command execution");
+ message.addField("Caused by command:", context.getCommand().getCommandName(), false);
+ message.addField("Exception:", error.getMessage(), false);
+ if (error.getCause() != null) {
+ String cause;
+ if (error.getCause().getMessage() != null && !error.getCause().getMessage().isEmpty()) {
+ cause = error.getCause().getMessage();
+ } else {
+ cause = error.getCause().getClass().getName();
+ }
+ message.addField("Caused by:", cause, false);
+ }
+
+ message.setFooter("Requested by " + context.getGuildUser().getEffectiveName(), context.getGuildUser().getAvatarUrl());
+ message.setTimestamp(OffsetDateTime.now());
+
+ return message.build();
+ }
+
+ public static MessageEmbed buildEditMessage(Member member, Message message, String originalMessage) {
+ EmbedBuilder editEmbed = new EmbedBuilder();
+ editEmbed.setColor(Color.RED);
+ editEmbed.setDescription(":pencil2: **Message edited by " + member.getEffectiveName() + "**");
+ try {
+ editEmbed.addField("Original message", (originalMessage == null ? "Not available" : originalMessage), false);
+ } catch (IllegalArgumentException ex) {
+ editEmbed.addField("Original message:", "Message too long.", false);
+ }
+
+ try {
+ editEmbed.addField("Edited to:", message.getContentDisplay(), false);
+ } catch (IllegalArgumentException ex) {
+ editEmbed.addField("Edited to:", "Message too long.", false);
+ }
+
+ editEmbed.setFooter("ID : " + message.getId(), null);
+ editEmbed.setTimestamp(message.getTimeCreated());
+
+ return editEmbed.build();
+ }
+
+ public static void testMessageAgainstBlacklist(Message message, MessageChannel channel, Set cache) {
+ boolean found = false;
+ for (String word : cache) {
+ if (message.getContentRaw().toLowerCase().contains(word)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ message.delete().queue();
+ channel.sendMessage("Not in my christian server").queue();
+ }
+ }
+
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/MiscUtils.java b/src/main/java/me/bobert/netneutrality/utils/MiscUtils.java
new file mode 100644
index 0000000..c4af9f3
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/MiscUtils.java
@@ -0,0 +1,25 @@
+package me.bobert.netneutrality.utils;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class MiscUtils {
+
+ public static boolean isValidUrl(String url) {
+ try {
+ URL object = new URL(url);
+ object.toURI();
+
+ return true;
+ } catch (MalformedURLException | URISyntaxException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java
new file mode 100644
index 0000000..47a36e7
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/MusicUtils.java
@@ -0,0 +1,49 @@
+package me.bobert.netneutrality.utils;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.core.context.CommandContext;
+import me.bobert.netneutrality.exceptions.CommandExecutionException;
+import me.bobert.netneutrality.music.MusicManager;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+
+public class MusicUtils {
+
+ public static boolean checkIfConnected(MusicManager manager, CommandContext context) {
+ if (context.getGuildUser().getVoiceState() == null) {
+ throw new CommandExecutionException("Voice state is null, this should not happen", context, null);
+ }
+
+ if (!context.getGuildUser().getVoiceState().inAudioChannel()) {
+ context.reply("You are not in a voice channel");
+ return false;
+ }
+
+ if (!manager.isConnected()) {
+ context.reply("The bot isn't connected to a voice channel");
+ return false;
+ }
+
+ VoiceChannel vChannel = (VoiceChannel) context.getGuildUser().getVoiceState().getChannel();
+ if (manager.isNotConnectedTo(vChannel)) {
+ context.reply("The bot isn't in your voice channel");
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean isUserDj(Member member) {
+ MusicManager manager = MusicManager.get(member.getGuild());
+
+ if (manager.isCurrentlyPlaying()) {
+ return manager.getRequesterOfCurrentTrack().equals(member);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/me/bobert/netneutrality/utils/Settings.java b/src/main/java/me/bobert/netneutrality/utils/Settings.java
new file mode 100644
index 0000000..e1084da
--- /dev/null
+++ b/src/main/java/me/bobert/netneutrality/utils/Settings.java
@@ -0,0 +1,106 @@
+package me.bobert.netneutrality.utils;
+
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import me.bobert.netneutrality.http.WebModule;
+import org.apache.logging.log4j.LogManager;
+import org.json.JSONArray;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+
+public class Settings {
+
+ private static Settings INSTANCE;
+ private final File propFile = new File("bot.properties");
+ private final Properties prop = new Properties();
+ public final String DATETIMEFORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ private Settings() {
+ checkAndLoadPropertiesFile();
+ loadEnvironmentVariables();
+ }
+
+ private void checkAndLoadPropertiesFile() {
+ try (FileInputStream fis = new FileInputStream(propFile)) {
+ prop.load(fis);
+ } catch (IOException e) {
+ LogManager.getLogger(Settings.class).info("Can't load settings from bot.properties, bot is most likely running in docker. Using environment variables instead.");
+ }
+ }
+
+ private void loadEnvironmentVariables() {
+ Map envs = System.getenv();
+
+ for (Map.Entry env : envs.entrySet()) {
+ prop.setProperty(env.getKey(), env.getValue());
+ }
+ }
+
+ public static Settings getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new Settings();
+ }
+
+ return INSTANCE;
+ }
+
+ public String get(String key) {
+ String property = prop.getProperty(key);
+
+ if (property == null) {
+ LogManager.getLogger(Settings.class).error("Can't find property with key {}", key);
+ }
+
+ return property;
+ }
+
+ public String get(String key, String def) {
+ String property = get(key);
+
+ if (property == null || property.isEmpty())
+ return def;
+
+ return property;
+ }
+
+ public String getMavenVersion() {
+ String version = null;
+
+ try {
+ Properties p = new Properties();
+ InputStream is = getClass().getResourceAsStream("/app.properties");
+ if (is != null) {
+ p.load(is);
+ version = p.getProperty("app.version", "");
+ }
+ } catch (Exception ignored) {}
+
+ if (version == null) {
+ version = "0.0.0";
+ }
+
+ return version;
+ }
+
+ public String getGitVersion() {
+ String gitlabHost = "https://git.bobert.me";
+ String gitlabProjectId = "22";
+ String latestReleaseUrl = String.format("%s/api/v4/projects/%s/releases/", gitlabHost, gitlabProjectId);
+
+ JSONArray json = new WebModule(latestReleaseUrl).request().getAsArray();
+ if (json.isNull(0)) {
+ return "0.0.0";
+ }
+
+ return json.getJSONObject(0).getString("tag_name");
+ }
+}
diff --git a/src/main/java/netneutrality/utils/UserFunction.java b/src/main/java/me/bobert/netneutrality/utils/UserFunction.java
similarity index 50%
rename from src/main/java/netneutrality/utils/UserFunction.java
rename to src/main/java/me/bobert/netneutrality/utils/UserFunction.java
index 5dd7ea6..f3ef127 100644
--- a/src/main/java/netneutrality/utils/UserFunction.java
+++ b/src/main/java/me/bobert/netneutrality/utils/UserFunction.java
@@ -1,18 +1,18 @@
-package netneutrality.utils;
+package me.bobert.netneutrality.utils;
-import net.dv8tion.jda.core.Permission;
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.Role;
+/*
+ * Copyright (c) anthorob
+ *
+ * https://discord.gg/qUa6zNFZsd
+ */
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Role;
public class UserFunction {
public static boolean isAdmin(Member user) {
-
- // This check will only be present during the development of the bot
- if (user.getUser().getId().equals(Settings.getCreator().getId())) {
- return true;
- }
-
boolean isAdmin = false;
for(Role role : user.getRoles()) {
if (role.getPermissions().contains(Permission.MANAGE_SERVER) || role.getPermissions().contains(Permission.ADMINISTRATOR))
diff --git a/src/main/java/netneutrality/MusicBot.java b/src/main/java/netneutrality/MusicBot.java
deleted file mode 100644
index 48d2223..0000000
--- a/src/main/java/netneutrality/MusicBot.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package netneutrality;
-
-import net.dv8tion.jda.core.AccountType;
-import net.dv8tion.jda.core.JDA;
-import net.dv8tion.jda.core.JDABuilder;
-import net.dv8tion.jda.core.entities.Game;
-import netneutrality.db.DBController;
-import netneutrality.db.controllers.DBSettings;
-import netneutrality.handler.ConsoleHandler;
-import netneutrality.handler.EditHandler;
-import netneutrality.handler.LogHandler;
-import netneutrality.handler.NewCommandListener;
-import netneutrality.http.server.WebApi;
-import netneutrality.music.MusicManager;
-import netneutrality.utils.Settings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.security.auth.login.LoginException;
-
-public class MusicBot {
-
- private static JDA jda;
- public static Logger LOGGER = LoggerFactory.getLogger(NewCommandListener.class);
-
- public static void main(String[] args) {
- System.setProperty("log4j.skipJansi", "false");
- DBController.init();
-
- String token = ((DBSettings) DBController.get("settings")).getSettingsByKey("token");
-
- try {
- jda = new JDABuilder(AccountType.BOT).setToken(token).buildBlocking();
- } catch (LoginException | IllegalArgumentException | InterruptedException e) {
- LOGGER.error(e.getMessage());
- LogHandler.log(e);
- }
- jda.getPresence().setGame(Game.watching("me load..."));
- jda.addEventListener(new NewCommandListener(), new EditHandler());
-
- Settings.setCreator(jda.getUserById("194270171727265792"));
- Settings.setOwnerGuild(jda.getGuildById("195386426400047105"));
-
- WebApi webserver = new WebApi();
-
- jda.getPresence().setGame(Game.watching("rape"));
- Thread status = new Thread(new StatusThread(jda.getPresence()));
- // status.start();
-
- ConsoleHandler consoleInput = new ConsoleHandler();
-
- switch (consoleInput.init()) {
-
- case "quit":
- System.out.println("Shutting down...");
- MusicManager.disconnectPlayers();
- jda.shutdown();
- DBController.shutdown();
- ThreadManager.shutdownExecutor();
- System.out.println("Shutdown complete");
- System.exit(0);
- break;
-
- }
-
- }
-
- public static JDA getJDA() {
- return jda;
- }
-
- public static void disconnect() {
- if (jda != null)
- jda.shutdown();
- }
-
-
-}
diff --git a/src/main/java/netneutrality/StatusThread.java b/src/main/java/netneutrality/StatusThread.java
deleted file mode 100644
index 1c8af3f..0000000
--- a/src/main/java/netneutrality/StatusThread.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package netneutrality;
-
-import net.dv8tion.jda.core.OnlineStatus;
-import net.dv8tion.jda.core.managers.Presence;
-
-public class StatusThread implements Runnable {
-
- private Presence status;
-
- public StatusThread(Presence pr) {
- this.status = pr;
- }
-
- @Override
- public void run() {
- try {
- while (true) {
- status.setStatus(OnlineStatus.ONLINE);
- Thread.sleep(10000);
- status.setStatus(OnlineStatus.IDLE);
- Thread.sleep(10000);
- status.setStatus(OnlineStatus.DO_NOT_DISTURB);
- Thread.sleep(10000);
- }
-
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- }
-
-}
diff --git a/src/main/java/netneutrality/ThreadManager.java b/src/main/java/netneutrality/ThreadManager.java
deleted file mode 100644
index 09a96c4..0000000
--- a/src/main/java/netneutrality/ThreadManager.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package netneutrality;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public class ThreadManager {
-
- public static final ExecutorService executor = Executors.newSingleThreadExecutor();
- public static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
-
-
-
- public static void shutdownExecutor() {
- try {
- System.out.println("attempt to shutdown executor");
- executor.shutdown();
- executor.awaitTermination(5, TimeUnit.SECONDS);
- }
- catch (InterruptedException e) {
- System.err.println("tasks interrupted");
- }
- finally {
- if (!executor.isTerminated()) {
- System.err.println("cancel non-finished tasks");
- }
- executor.shutdownNow();
- System.out.println("shutdown finished");
- }
- }
-}
diff --git a/src/main/java/netneutrality/command/AbstractCommand.java b/src/main/java/netneutrality/command/AbstractCommand.java
deleted file mode 100644
index c3c00f6..0000000
--- a/src/main/java/netneutrality/command/AbstractCommand.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package netneutrality.command;
-
-import java.util.Arrays;
-import java.util.List;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-
-public abstract class AbstractCommand {
-
- public AbstractCommand() {
-
- }
-
- /**
- * @return if the command needs administrator privilege
- */
- public abstract boolean needAdmin();
-
- /**
- * @return if the command is enabled
- */
- public abstract boolean isEnabled();
-
- /**
- * The command name that a user will write in a channel
- *
- * @return the name of the command.
- */
- public abstract String getCommand();
-
- /**
- * @return all the aliases the command has
- */
- public abstract String[] getAlias();
-
- public abstract String getUsage();
-
- public abstract Visibility getVisibility();
-
- /**
- * @param event
- * @param args the arguments to the command
- * @return
- */
- public abstract String execute(MessageReceivedEvent event, String args);
-
- public int countArgs(String command) {
- if (command.isEmpty())
- return 0;
-
- List args = Arrays.asList(command.split(" "));
- return args.size();
- }
-
- public String parseArgs(String command, int arg) {
- if (command.isEmpty())
- return "";
-
- List args = Arrays.asList(command.split(" "));
- return args.get(arg);
- }
-}
diff --git a/src/main/java/netneutrality/command/CommandManager.java b/src/main/java/netneutrality/command/CommandManager.java
deleted file mode 100644
index 58aea94..0000000
--- a/src/main/java/netneutrality/command/CommandManager.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package netneutrality.command;
-
-/*
- * Copyright (c) anthorob
- *
- * https://xtremcommunity.com/
- * https://discord.gg/CdFcyQ
- */
-
-import netneutrality.MusicBot;
-import org.reflections.Reflections;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.util.HashMap;
-import java.util.Set;
-
-public class CommandManager {
-
- public static HashMap commands = new HashMap<>();
- public static HashMap commandsAlias = new HashMap<>();
-
- public static void init() {
- initCommands();
- initAlias();
- }
-
- private static void initCommands() {
- MusicBot.LOGGER.info("Loading commands..");
-
- Reflections reflections = new Reflections("netneutrality.command");
- Set> classes = reflections.getSubTypesOf(AbstractCommand.class);
-
- for (Class extends AbstractCommand> s : classes) {
- try {
- if (Modifier.isAbstract(s.getModifiers())) {
- continue;
- }
-
- AbstractCommand command = s.getConstructor().newInstance();
-
- if (!command.isEnabled())
- continue;
-
- if (!commands.containsKey(command.getCommand())) {
- commands.put(command.getCommand(), command);
- }
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- e.printStackTrace();
- }
- }
-
- MusicBot.LOGGER.info("Loaded " + commands.size() + " commands!");
- }
-
- private static void initAlias() {
-
- for (AbstractCommand command : commands.values()) {
- for (String alias : command.getAlias()) {
- if (!commandsAlias.containsKey(alias))
- commandsAlias.put(alias, command);
- }
- }
- }
-
-}
diff --git a/src/main/java/netneutrality/command/Visibility.java b/src/main/java/netneutrality/command/Visibility.java
deleted file mode 100644
index 03723e6..0000000
--- a/src/main/java/netneutrality/command/Visibility.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package netneutrality.command;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.MessageChannel;
-import net.dv8tion.jda.core.entities.PrivateChannel;
-import net.dv8tion.jda.core.entities.TextChannel;
-
-public enum Visibility {
-
- PRIVATE, CHANNEL, BOTH;
-
- public static boolean check(MessageChannel channel, Visibility v) {
- if (v == BOTH && channel instanceof TextChannel || channel instanceof PrivateChannel)
- return true;
-
- if (v == PRIVATE && channel instanceof PrivateChannel)
- return true;
-
- if (v == CHANNEL && channel instanceof TextChannel)
- return true;
-
- return false;
- }
-}
diff --git a/src/main/java/netneutrality/command/basic/HelpCommand.java b/src/main/java/netneutrality/command/basic/HelpCommand.java
deleted file mode 100644
index 4adc295..0000000
--- a/src/main/java/netneutrality/command/basic/HelpCommand.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package netneutrality.command.basic;
-
-import java.awt.Color;
-import java.util.HashMap;
-
-import net.dv8tion.jda.core.EmbedBuilder;
-import net.dv8tion.jda.core.MessageBuilder;
-import net.dv8tion.jda.core.entities.MessageChannel;
-import net.dv8tion.jda.core.entities.TextChannel;
-import net.dv8tion.jda.core.entities.User;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.utils.Settings;
-
-public class HelpCommand extends AbstractCommand {
-
- private String help = "";
-
- public HelpCommand() {
-
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- EmbedBuilder embed = new EmbedBuilder();
- embed.setColor(Color.green);
- embed.addField("Commands for Net Neutrality", help, false);
-
- event.getTextChannel().sendMessage(new MessageBuilder().append(event.getAuthor()).append(" help has been sent your way!").build()).queue();
-
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage(embed.build()).queue());
- return null;
- }
-
- public void sendHelp(HashMap commands, HashMap alias, User user, MessageChannel channel) {
- for (AbstractCommand command : commands.values()) {
- if (command.getUsage() != null)
- help += Settings.PREFIX + command.getCommand() + " (: " + command.getUsage() + "\n";
- }
-
-
- EmbedBuilder embed = new EmbedBuilder();
- embed.setColor(Color.green);
- embed.addField("Commands for Net Neutrality", help, false);
-
- if (channel instanceof TextChannel)
- channel.sendMessage(new MessageBuilder().append(user).append(" help has been sent your way!").build()).queue();
- user.openPrivateChannel().queue(t -> t.sendMessage(embed.build()).queue());
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "help";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return "Get every command with its usage";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
-
-}
diff --git a/src/main/java/netneutrality/command/basic/PingCommand.java b/src/main/java/netneutrality/command/basic/PingCommand.java
deleted file mode 100644
index 2bff225..0000000
--- a/src/main/java/netneutrality/command/basic/PingCommand.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package netneutrality.command.basic;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.MusicBot;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-
-public class PingCommand extends AbstractCommand {
-
- public PingCommand() {
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- if (countArgs(args) == 0)
- event.getChannel().sendMessage("Pong! [" + MusicBot.getJDA().getPing() + " ms]").queue();
-
- return null;
-
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "ping";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return "Pong!";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java b/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java
deleted file mode 100644
index 1156a69..0000000
--- a/src/main/java/netneutrality/command/crypto/BitcoinPriceCommand.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package netneutrality.command.crypto;
-
-import java.io.IOException;
-import java.util.function.Consumer;
-
-import netneutrality.command.Visibility;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import net.dv8tion.jda.core.entities.Message;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.http.WebModule;
-
-public class BitcoinPriceCommand extends AbstractCommand {
-
- private String currency = "USD";
- private String currencyUrl = "https://api.coindesk.com/v1/bpi/currentprice/";
- private String price;
-
- public BitcoinPriceCommand() {
- this.currency = "USD";
- }
-
- private String messageBuilder(String price) {
- String newPrice = price.substring(0, price.indexOf("."));
-
- return "The current bitcoin price in " + this.currency + " is " + newPrice + "$";
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) throws JSONException, ArrayIndexOutOfBoundsException {
-
- if (countArgs(args) == 1) {
- if (Currency.isCurrency(args.toUpperCase()))
- this.currency = args.toUpperCase();
- else {
- event.getTextChannel().sendMessage("Please enter a valid currency").queue();
- return null;
- }
-
- }
-
- event.getTextChannel().sendMessage("Retreiving price, please wait...").queue(new Consumer() {
-
- @Override
- public void accept(Message t) {
- try {
-
- WebModule bitcoinHttp = new WebModule(currencyUrl + currency + ".json").request();
- JSONObject bpi = bitcoinHttp.getAsObject().getJSONObject("bpi");
- price = bpi.getJSONObject(currency.toUpperCase()).getString("rate");
-
- } catch (IOException | RuntimeException e) {
- e.printStackTrace();
- t.editMessage("Error getting bitcoin price!").queue();
- }
-
- t.editMessage(messageBuilder(price)).queue();
-
- }
- });
-
-
-
- return null;
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "bitcoin";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[] {"btc"};
- }
-
- @Override
- public String getUsage() {
- return "Get the bitcoin price";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/crypto/Currency.java b/src/main/java/netneutrality/command/crypto/Currency.java
deleted file mode 100644
index 8447223..0000000
--- a/src/main/java/netneutrality/command/crypto/Currency.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package netneutrality.command.crypto;
-
-public enum Currency {
-
- AED("AED"), AFN("AFN"), ALL("ALL"), AMD("AMD"), ANG("ANG"), AOA("AOA"), ARS("ARS"), AUD("AUD"), AWG("AWG"), AZN("AZN"), BAM("BAM"), BBD("BBD"),
- BDT("BDT"), BGN("BGN"), BHD("BHD"), BIF("BIF"), BMD("BMD"), BND("BND"), BOB("BOB"), BOV("BOV"), BRL("BRL"), BSD("BSD"), BTN("BTN"), BWP("BWP"),
- BYN("BYN"), BYR("BYR"), BZD("BZD"), CAD("CAD"), CDF("CDF"), CHE("CHE"), CHF("CHF"), CHW("CHW"), CLF("CLF"), CLP("CLP"), CNY("CNY"), COP("COP"),
- COU("COU"), CRC("CRC"), CUC("CUC"), CUP("CUP"), CVE("CVE"), CZK("CZK"), DJF("DJF"), DKK("DKK"), DOP("DOP"), DZD("DZD"), EGP("EGP"), ERN("ERN"),
- ETB("ETB"), EUR("EUR"), FJD("FJD"), FKP("FKP"), GBP("GBP"), GEL("GEL"), GHS("GHS"), GIP("GIP"), GMD("GMD"), GNF("GNF"), GTQ("GTQ"), GYD("GYD"),
- HKD("HKD"), HNL("HNL"), HRK("HRK"), HTG("HTG"), HUF("HUF"), IDR("IDR"), ILS("ILS"), INR("INR"), IQD("IQD"), IRR("IRR"), ISK("ISK"), JMD("JMD"),
- JOD("JOD"), JPY("JPY"), KES("KES"), KGS("KGS"), KHR("KHR"), KMF("KMF"), KPW("KPW"), KRW("KRW"), KWD("KWD"), KYD("KYD"), KZT("KZT"), LAK("LAK"),
- LBP("LBP"), LKR("LKR"), LRD("LRD"), LSL("LSL"), LYD("LYD"), MAD("MAD"), MDL("MDL"), MGA("MGA"), MKD("MKD"), MMK("MMK"), MNT("MNT"), MOP("MOP"),
- MRO("MRO"), MUR("MUR"), MVR("MVR"), MWK("MWK"), MXN("MXN"), MXV("MXV"), MYR("MYR"), MZN("MZN"), NAD("NAD"), NGN("NGN"), NIO("NIO"), NOK("NOK"),
- NPR("NPR"), NZD("NZD"), OMR("OMR"), PAB("PAB"), PEN("PEN"), PGK("PGK"), PHP("PHP"), PKR("PKR"), PLN("PLN"), PYG("PYG"), QAR("QAR"), RON("RON"),
- RSD("RSD"), RUB("RUB"), RWF("RWF"), SAR("SAR"), SBD("SBD"), SCR("SCR"), SDG("SDG"), SEK("SEK"), SGD("SGD"), SHP("SHP"), SLL("SLL"), SOS("SOS"),
- SRD("SRD"), SSP("SSP"), STD("STD"), SYP("SYP"), SZL("SZL"), THB("THB"), TJS("TJS"), TMT("TMT"), TND("TND"), TOP("TOP"), TRY("TRY"), TTD("TTD"),
- TWD("TWD"), TZS("TZS"), UAH("UAH"), UGX("UGX"), USD("USD"), USN("USN"), UYI("UYI"), UYU("UYU"), UZS("UZS"), VEF("VEF"), VND("VND"), VUV("VUV"),
- WST("WST"), XAF("XAF"), XAG("XAG"), XAU("XAU"), XBA("XBA"), XBB("XBB"), XBC("XBC"), XBD("XBD"), XCD("XCD"), XDR("XDR"), XFU("XFU"), XOF("XOF"),
- XPD("XPD"), XPF("XPF"), XPT("XPT"), XSU("XSU"), XTS("XTS"), XUA("XUA"), XXX("XXX"), YER("YER"), ZAR("ZAR"), ZMW("ZMW");
-
- private String iso4217Code;
-
- Currency(String code) {
- this.iso4217Code = code;
- }
-
- @Override
- public String toString() {
- return iso4217Code;
- }
-
- public static boolean isCurrency(String str) {
- for (Currency c : Currency.values()) {
- if (c.name().equals(str.toUpperCase()))
- return true;
- }
-
- return false;
- }
-}
diff --git a/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java b/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java
deleted file mode 100644
index 5852dc3..0000000
--- a/src/main/java/netneutrality/command/crypto/EthereumPriceCommand.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package netneutrality.command.crypto;
-
-import java.io.IOException;
-import java.util.function.Consumer;
-
-import netneutrality.command.Visibility;
-import org.json.JSONException;
-
-import net.dv8tion.jda.core.entities.Message;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.http.WebModule;
-
-public class EthereumPriceCommand extends AbstractCommand {
-
- private String currency = "USD";
- private String currencyUrl = "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=";
- private String price = "";
-
- public EthereumPriceCommand() {
- this.currency = "USD";
- }
-
- private String messageBuilder(String price) {
- return "The current ethereum price in " + this.currency + " is " + price + "$";
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) throws JSONException, ArrayIndexOutOfBoundsException {
-
- if (countArgs(args) == 1) {
- if (Currency.isCurrency(args.toUpperCase()))
- this.currency = args.toUpperCase();
- else {
- event.getTextChannel().sendMessage("Please enter a valid currency").queue();
- return null;
- }
-
- }
-
- event.getTextChannel().sendMessage("Retreiving price, please wait...").queue(new Consumer() {
-
- @Override
- public void accept(Message t) {
-
- try {
- WebModule ethereumHttp = new WebModule(currencyUrl + currency.toUpperCase()).request();
- price = Double.toString(ethereumHttp.getAsObject().getDouble(currency));
-
- } catch (IOException | RuntimeException e) {
- e.printStackTrace();
- t.editMessage("Error getting ethereum price!").queue();
- }
- t.editMessage(messageBuilder(price)).queue();
- }
- });
-
- return null;
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "ethereum";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[] {"eth"};
- }
-
- @Override
- public String getUsage() {
- return "Get the ethereum price";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java b/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java
deleted file mode 100644
index 5636395..0000000
--- a/src/main/java/netneutrality/command/crypto/LitecoinPriceCommand.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package netneutrality.command.crypto;
-
-import java.io.IOException;
-import java.util.function.Consumer;
-
-import netneutrality.command.Visibility;
-import org.json.JSONException;
-
-import net.dv8tion.jda.core.entities.Message;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.http.WebModule;
-
-public class LitecoinPriceCommand extends AbstractCommand {
-
- private String currency = "USD";
- private String currencyUrl = "https://api.coinmarketcap.com/v1/ticker/litecoin/?convert=";
- private String price;
-
- public LitecoinPriceCommand() {
- this.currency = "USD";
- }
-
- private String messageBuilder(String price) {
- return "The current litecoin price in " + this.currency + " is " + price + "$";
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) throws ArrayIndexOutOfBoundsException, JSONException {
-
- if (countArgs(args) == 1) {
- if (Currency.isCurrency(args.toUpperCase()))
- this.currency = args;
- else {
- event.getTextChannel().sendMessage("Please enter a valid currency").queue();
- return null;
- }
-
- }
-
- event.getTextChannel().sendMessage("Retreiving price, please wait...").queue(new Consumer() {
-
- @Override
- public void accept(Message t) {
-
- try {
- WebModule litecoinHttp = new WebModule(currencyUrl + currency).request();
- price = litecoinHttp.getAsArray().getJSONObject(0).getString("price_" + currency.toLowerCase());
- } catch (IOException | RuntimeException e) {
- e.printStackTrace();
- t.editMessage("Error getting litecoin price!").queue();
- }
- t.editMessage(messageBuilder(price)).queue();
-
- }
- });
-
- return null;
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "litecoin";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[] {"ltc"};
- }
-
- @Override
- public String getUsage() {
- return "Get the litecoin price";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-}
diff --git a/src/main/java/netneutrality/command/message/BlacklistCommand.java b/src/main/java/netneutrality/command/message/BlacklistCommand.java
deleted file mode 100644
index 6631496..0000000
--- a/src/main/java/netneutrality/command/message/BlacklistCommand.java
+++ /dev/null
@@ -1,141 +0,0 @@
-package netneutrality.command.message;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.db.DBController;
-import netneutrality.db.controllers.DBBlacklist;
-import netneutrality.db.models.MBlacklist;
-import netneutrality.handler.LogHandler;
-
-public class BlacklistCommand extends AbstractCommand {
-
- public BlacklistCommand() {
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "blacklist";
- }
-
- @Override
- public String[] getAlias() {
- return new String[] {"bl"};
- }
-
- @Override
- public String getUsage() {
- return "Manages the blacklist of your server.";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
-
- if (countArgs(args) > 0 && countArgs(args) < 3) {
- String word = (countArgs(args) == 2 ? parseArgs(args, 1) : "");
-
- switch (parseArgs(args, 0)) {
-
- case "get":
- event.getTextChannel().sendMessage("Retreiving list, please wait...").queue(t -> {
-
-
- try {
- StringBuilder sb = new StringBuilder();
- ((DBBlacklist) DBController.get("blacklist")).getBlacklistFromServer(event.getGuild().getId(), new Consumer>() {
- @Override
- public void accept(ArrayList mBlacklists) {
- for (MBlacklist bl : mBlacklists) {
- sb.append(bl.getWord()).append(" -> ").append(bl.isEnabled() ? "enabled" : "disabled").append("\n");
- }
-
- t.editMessage("List of blacklisted words for this server [" + event.getGuild().getName() + "] :\n" + (mBlacklists.isEmpty() ? "No word found" : sb.toString())).queue();
- }
- });
-
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
-
-
- });
-
- break;
-
- case "add":
-
- try {
- ((DBBlacklist) DBController.get("blacklist")).add(new MBlacklist(word, true, event.getAuthor().getId(), event.getGuild().getId()));
- event.getTextChannel().sendMessage("Added the word \"" + word + "\" to the blacklist.").queue();
- } catch (RuntimeException e) {
- event.getTextChannel().sendMessage("Error adding \"" + word + "\" to the blacklist. It's already blacklisted!").queue();
- LogHandler.log(e);
- }
-
- break;
-
- case "remove":
-
- try {
- ((DBBlacklist) DBController.get("blacklist")).delete(word, event.getGuild().getId());
- event.getTextChannel().sendMessage("Removed the word \"" + word + "\" from the blacklist.").queue();
- } catch (RuntimeException e) {
- LogHandler.log(e);
- event.getTextChannel().sendMessage("Error removing \"" + word + "\" from the blacklist").queue();
- }
-
- break;
-
- case "disable":
-
- try {
- ((DBBlacklist) DBController.get("blacklist")).disable(word, event.getGuild().getId());
- event.getTextChannel().sendMessage("Succesfully disabled the word \"" + word + "\" from the blacklist!").queue();
- } catch (NullPointerException e) {
- LogHandler.log(e);
- event.getTextChannel().sendMessage("Error disabling \"" + word + "\", it's not blacklisted!").queue();
- }
-
- break;
-
- case "enable":
-
- try {
- ((DBBlacklist) DBController.get("blacklist")).enable(word, event.getGuild().getId());
- event.getTextChannel().sendMessage("Succesfully enabled the word \"" + word + "\" from the blacklist!").queue();
- } catch (NullPointerException e) {
- event.getTextChannel().sendMessage("Error enabling \"" + word + "\", it's not blacklisted!").queue();
- LogHandler.log(e);
- }
-
- break;
-
- }
- }
-
- return null;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/message/PruneCommand.java b/src/main/java/netneutrality/command/message/PruneCommand.java
deleted file mode 100644
index 21d84d1..0000000
--- a/src/main/java/netneutrality/command/message/PruneCommand.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package netneutrality.command.message;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.utils.ChannelFunction;
-
-public class PruneCommand extends AbstractCommand {
-
- public PruneCommand() {
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "prune";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[] {"purge"};
- }
-
- @Override
- public String getUsage() {
- return "Prune n messages from the channel you are currently on";
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- if (countArgs(args) == 1)
- ChannelFunction.bulkDeleteMessage(event.getTextChannel(), Integer.parseInt(args));
- else
- event.getTextChannel().sendMessage("Failed to delete messages!").queue();
-
- return null;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/music/BitconnectCommand.java b/src/main/java/netneutrality/command/music/BitconnectCommand.java
deleted file mode 100644
index 47e6647..0000000
--- a/src/main/java/netneutrality/command/music/BitconnectCommand.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class BitconnectCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "bitconnect";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
- player.play("https://www.youtube.com/watch?v=A8M70M7tzTI", event.getTextChannel(), true);
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else {
- player.connect(vChannel);
- player.play("https://www.youtube.com/watch?v=A8M70M7tzTI", event.getTextChannel(), true);
- }
-
-
-
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- event.getMessage().delete().queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/DeleteQueueCommand.java b/src/main/java/netneutrality/command/music/DeleteQueueCommand.java
deleted file mode 100644
index ad99c3f..0000000
--- a/src/main/java/netneutrality/command/music/DeleteQueueCommand.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class DeleteQueueCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "deletequeue";
- }
-
- @Override
- public String[] getAlias() {
- return new String[]{"delq"};
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
- int queueSize = player.getScheduler().queue.size();
- if (queueSize > 0) {
- player.getScheduler().queue.clear();
- event.getTextChannel().sendMessage("Removed " + queueSize + (queueSize > 1 ? " tracks" : "track") + " from the queue").queue();
- } else {
- event.getTextChannel().sendMessage("Queue is empty!").queue();
- }
-
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else {
- event.getTextChannel().sendMessage("Not in a voice channel").queue();
- }
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/JoinCommand.java b/src/main/java/netneutrality/command/music/JoinCommand.java
deleted file mode 100644
index ef9ae4f..0000000
--- a/src/main/java/netneutrality/command/music/JoinCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class JoinCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "join";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (!player.isConnected()) {
- if (!player.isConnectedTo(vChannel)) {
- player.connect(vChannel);
- } else
- event.getTextChannel().sendMessage("Already in a voice channel").queue();
- } else
- event.getTextChannel().sendMessage("Already in a voice channel").queue();
-
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/LeaveCommand.java b/src/main/java/netneutrality/command/music/LeaveCommand.java
deleted file mode 100644
index 8794c4e..0000000
--- a/src/main/java/netneutrality/command/music/LeaveCommand.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class LeaveCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "leave";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnectedTo(vChannel)) {
- player.leave();
- }
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/PlayCommand.java b/src/main/java/netneutrality/command/music/PlayCommand.java
deleted file mode 100644
index 76d1068..0000000
--- a/src/main/java/netneutrality/command/music/PlayCommand.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class PlayCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "play";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
- event.getMessage().delete().queue();
-
- if (args.startsWith("ytsearch:")) {
- player.play(args, event.getTextChannel(), false);
- } else {
- player.play(args.split(" ")[0], event.getTextChannel(), false);
- }
-
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else {
- player.connect(vChannel);
- event.getMessage().delete().queue();
- player.play(args.split(" ")[0], event.getTextChannel(), false);
- }
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/SkipCommand.java b/src/main/java/netneutrality/command/music/SkipCommand.java
deleted file mode 100644
index de217fa..0000000
--- a/src/main/java/netneutrality/command/music/SkipCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class SkipCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "skip";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
- player.skip();
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else
- event.getTextChannel().sendMessage("Not in a voice channel").queue();
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/StopCommand.java b/src/main/java/netneutrality/command/music/StopCommand.java
deleted file mode 100644
index 9ae2ac0..0000000
--- a/src/main/java/netneutrality/command/music/StopCommand.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class StopCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "stop";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
- player.stopMusic();
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else
- event.getTextChannel().sendMessage("Not in a voice channel").queue();
-
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/music/VolumeCommand.java b/src/main/java/netneutrality/command/music/VolumeCommand.java
deleted file mode 100644
index 59d212d..0000000
--- a/src/main/java/netneutrality/command/music/VolumeCommand.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package netneutrality.command.music;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.entities.Member;
-import net.dv8tion.jda.core.entities.VoiceChannel;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-
-public class VolumeCommand extends AbstractCommand {
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "volume";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- Member member = event.getMember();
- MusicManager player = MusicManager.get(event.getGuild());
-
- if (member.getVoiceState().inVoiceChannel()) {
- VoiceChannel vChannel = member.getVoiceState().getChannel();
-
- if (player.isConnected()) {
- if (player.isConnectedTo(vChannel)) {
-
- try {
- int volume = Integer.parseInt(args.split(" ")[0]);
- player.setVolume(volume);
- } catch (NumberFormatException e) {
- e.printStackTrace();
- }
-
- } else
- event.getTextChannel().sendMessage("Not in your voice channel").queue();
- } else
- event.getTextChannel().sendMessage("Not in a voice channel").queue();
-
- } else
- event.getTextChannel().sendMessage("You are not in a voice channel").queue();
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/specific/DatabaseCommand.java b/src/main/java/netneutrality/command/specific/DatabaseCommand.java
deleted file mode 100644
index 3a7164a..0000000
--- a/src/main/java/netneutrality/command/specific/DatabaseCommand.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package netneutrality.command.specific;
-
-import java.awt.Color;
-import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-import net.dv8tion.jda.core.EmbedBuilder;
-import net.dv8tion.jda.core.entities.PrivateChannel;
-import net.dv8tion.jda.core.entities.User;
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.db.DBController;
-import netneutrality.db.controllers.DBMessage;
-import netneutrality.db.models.MMessage;
-import netneutrality.utils.Settings;
-
-public class DatabaseCommand extends AbstractCommand {
-
- public DatabaseCommand() {
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "database";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[] { "db" };
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
-
- if (!event.getAuthor().getDiscriminator().equals(Settings.getCreator().getDiscriminator()))
- return null;
-
- if (countArgs(args) > 0) {
- switch (parseArgs(args, 0)) {
-
- case "message":
-
- if (countArgs(args) > 1) {
- event.getMessage().delete().queue();
- MMessage message = ((DBMessage) DBController.get("message")).getMessage(parseArgs(args, 1));
- if (message != null) {
- EmbedBuilder embed = new EmbedBuilder();
- embed.setColor(Color.BLUE);
- embed.setTimestamp(
- DateTimeFormatter.ofPattern(Settings.DATETIMEFORMAT).parse(message.getDateSent()));
- embed.setFooter("Guild : " + message.getServerName() + " | ID : " + message.getMessageId(), null);
- embed.setDescription(
- "Message from " + message.getSender() + " on the channel " + message.getChannelName());
- embed.addField("Message: ", "" + message.getMessage(), false);
- event.getTextChannel().sendMessage(embed.build()).queue();
- } else {
- User user = event.getJDA().getUserById(parseArgs(args, 1));
- if (user != null) {
- ArrayList messages = ((DBMessage) DBController.get("message"))
- .getMessagesFromUser(user.getName(), event.getGuild().getName(), 10);
- EmbedBuilder embed = new EmbedBuilder();
- embed.setColor(Color.BLUE);
- embed.setTimestamp(OffsetDateTime.now());
- for (MMessage message1 : messages) {
- embed.addField(
- "Message in channel \"" + message1.getChannelName() + "\" sent on "
- + message1.getDateSent(),
- "" + message1.getMessage(), false);
- }
- embed.setFooter("Guild : " + event.getGuild().getName(), null);
-
- event.getAuthor().openPrivateChannel().queue(new Consumer() {
-
- @Override
- public void accept(PrivateChannel t) {
- t.sendMessage(embed.build()).queue();
-
- }
- });
- }
- }
-
- }
-
- break;
- }
- }
-
- return null;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/specific/EvalMethod.java b/src/main/java/netneutrality/command/specific/EvalMethod.java
deleted file mode 100644
index 1ec4a28..0000000
--- a/src/main/java/netneutrality/command/specific/EvalMethod.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package netneutrality.command.specific;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.music.MusicManager;
-import netneutrality.utils.Settings;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-public class EvalMethod extends AbstractCommand {
-
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "evaluate";
- }
-
- @Override
- public String[] getAlias() {
- return new String[] {"eval"};
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
-
- if (event.getAuthor().getId().equals(Settings.getCreator().getId())) {
- ScriptEngine se = new ScriptEngineManager().getEngineByName("Nashorn");
- try {
- se.eval("var imports = new JavaImporter(java.io, java.lang, java.util);");
- } catch (ScriptException e) {
- e.printStackTrace();
- }
-
- se.put("event", event);
- se.put("jda", event.getJDA());
- se.put("guild", event.getGuild());
- se.put("channel", event.getChannel());
- se.put("member", event.getMember());
- se.put("music", MusicManager.get(event.getGuild()));
-
- try {
- event.getTextChannel().sendMessage(" Evaluated Successfully:\n```\n"+se.eval("(function() {with (imports) {\nreturn " + args + "\n}})();") + " ```").queue();
- }
- catch(Exception e) {
- event.getTextChannel().sendMessage(" An exception was thrown:\n```\n"+e+" ```").queue();
- }
- }
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/specific/LoggingCommand.java b/src/main/java/netneutrality/command/specific/LoggingCommand.java
deleted file mode 100644
index a275657..0000000
--- a/src/main/java/netneutrality/command/specific/LoggingCommand.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package netneutrality.command.specific;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-
-public class LoggingCommand extends AbstractCommand {
-
- public LoggingCommand() {
-
- }
-
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return false;
- }
-
- @Override
- public String getCommand() {
- return "logging";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
-
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/specific/NiggerCommand.java b/src/main/java/netneutrality/command/specific/NiggerCommand.java
deleted file mode 100644
index 9d33f19..0000000
--- a/src/main/java/netneutrality/command/specific/NiggerCommand.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package netneutrality.command.specific;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-
-public class NiggerCommand extends AbstractCommand {
-
- public NiggerCommand() {
-
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- event.getMessage().delete().queue();
- event.getTextChannel().sendMessage("Not in my christian server").queue();
- return null;
-
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return "nigger";
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/specific/SellyCommand.java b/src/main/java/netneutrality/command/specific/SellyCommand.java
deleted file mode 100644
index 7d6ae9d..0000000
--- a/src/main/java/netneutrality/command/specific/SellyCommand.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package netneutrality.command.specific;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.db.DBController;
-import netneutrality.db.controllers.DBSettings;
-import netneutrality.db.models.MSellyOrder;
-import netneutrality.http.Selly;
-
-import java.util.Map;
-
-public class SellyCommand extends AbstractCommand {
-
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "selly";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.PRIVATE;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
-
- String sellyEmail = ((DBSettings) DBController.get("settings")).getSettingsByKey("selly_email");
- String sellyKey = ((DBSettings) DBController.get("settings")).getSettingsByKey("");
- Selly selly = new Selly(sellyEmail, sellyKey);
-
- Map orders = selly.getOrders();
-
- System.out.println(selly.getProducts().toString());
-
- if (countArgs(args) > 1) {
- String email = parseArgs(args, 0);
- String id = parseArgs(args, 1);
-
- if (orders.containsKey(id)) {
- MSellyOrder order = orders.get(id);
- if (email.equals(order.getEmail())) {
- if (order.getStatus() == 100) {
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage("success").queue());
- } else
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage("not completed").queue());
- } else
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage("wrong email").queue());
- } else
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage("order doesn't exist").queue());
- } else
- event.getAuthor().openPrivateChannel().queue(t -> t.sendMessage("usage : $selly ").queue());
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/specific/ShutdownCommand.java b/src/main/java/netneutrality/command/specific/ShutdownCommand.java
deleted file mode 100644
index 8536f2e..0000000
--- a/src/main/java/netneutrality/command/specific/ShutdownCommand.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package netneutrality.command.specific;
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-
-public class ShutdownCommand extends AbstractCommand {
-
- public ShutdownCommand() {
- // TODO Auto-generated constructor stub
- }
-
- @Override
- public boolean needAdmin() {
- // TODO Auto-generated method stub
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public String getCommand() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public String[] getAlias() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- event.getJDA().shutdown();
- System.exit(0);
- return null;
- }
-
-}
diff --git a/src/main/java/netneutrality/command/specific/TestCommand.java b/src/main/java/netneutrality/command/specific/TestCommand.java
deleted file mode 100644
index 15b616e..0000000
--- a/src/main/java/netneutrality/command/specific/TestCommand.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package netneutrality.command.specific;
-
-/*
- * Copyright (c) anthorob
- *
- * This file is closed-source. It should not be distributed or copied in any way or form
- */
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.utils.MessageFunction;
-import netneutrality.utils.Settings;
-
-public class TestCommand extends AbstractCommand {
-
- public TestCommand() {
-
- }
-
- @Override
- public boolean needAdmin() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "test";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.BOTH;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- if (event.getAuthor().getDiscriminator().equals(Settings.getCreator().getDiscriminator())) {
- MessageFunction.isStartingWithMention(event.getMessage());
- }
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/command/warframe/JoinClanCommand.java b/src/main/java/netneutrality/command/warframe/JoinClanCommand.java
deleted file mode 100644
index 102ee32..0000000
--- a/src/main/java/netneutrality/command/warframe/JoinClanCommand.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package netneutrality.command.warframe;
-
-/*
- * Copyright (c) anthorob
- *
- * https://xtremcommunity.com/
- * https://discord.gg/CdFcyQ
- */
-
-import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
-import netneutrality.command.AbstractCommand;
-import netneutrality.command.Visibility;
-import netneutrality.db.DBController;
-import netneutrality.db.controllers.DBLogging;
-
-public class JoinClanCommand extends AbstractCommand {
-
- String roleId = "458372887619829770";
-
- @Override
- public boolean needAdmin() {
- return false;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public String getCommand() {
- return "joinclan";
- }
-
- @Override
- public String[] getAlias() {
- return new String[0];
- }
-
- @Override
- public String getUsage() {
- return null;
- }
-
- @Override
- public Visibility getVisibility() {
- return Visibility.CHANNEL;
- }
-
- @Override
- public String execute(MessageReceivedEvent event, String args) {
- event.getMessage().delete().queue();
-
- ((DBLogging) DBController.get("logging")).getLoggingChannel(event.getGuild().getId(), logging -> {
-
- if (logging != null) {
- event.getGuild().getController().addRolesToMember(event.getMember(), event.getGuild().getRoleById(roleId)).queue();
- logging.sendMessage("Added user \"" + event.getMember().getEffectiveName() + "\" to the role \"Clan Member\"").queue();
- }
- });
-
- return null;
- }
-}
diff --git a/src/main/java/netneutrality/db/AbstractController.java b/src/main/java/netneutrality/db/AbstractController.java
deleted file mode 100644
index 75fd1d5..0000000
--- a/src/main/java/netneutrality/db/AbstractController.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package netneutrality.db;
-
-public abstract class AbstractController {
-
- protected MysqlDatabase database;
-
- /**
- * The controller name to be used in {@link DBController} get(String controller) method
- *
- * @return String
- */
- public abstract String getControllerName();
-
- protected void setDatabase(MysqlDatabase database) {
- this.database = database;
- }
-}
diff --git a/src/main/java/netneutrality/db/DBController.java b/src/main/java/netneutrality/db/DBController.java
deleted file mode 100644
index 07e0656..0000000
--- a/src/main/java/netneutrality/db/DBController.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package netneutrality.db;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Modifier;
-import java.sql.SQLException;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import netneutrality.MusicBot;
-import netneutrality.ThreadManager;
-import org.reflections.Reflections;
-
-/**
- * When creating a controller, all database actions must be executed in another thread using an executor. One is provided in the {@link ThreadManager} class
- * A simple thread can't be used in this case because unpredictable errors might happen and some actions might not be executed.
- */
-public class DBController {
-
- /**
- * A map that contains all the controllers currently loaded
- */
- private final static HashMap controllers = new HashMap<>();
-
-
- private static MysqlDatabase database = new MysqlDatabase("discord");
-
- /**
- * @param controller The name of the controller as defined in the {@link AbstractController#getControllerName()}'s method
- * @return {@link AbstractController} The class with an AbstractController type
- */
- public static AbstractController get(String controller) {
-
- if (controllers.isEmpty())
- init();
-
- if (database.isClosed())
- database.reconnect();
-
- if (controllers.containsKey(controller)) {
- return controllers.get(controller);
- }
-
- return null;
- }
-
- public static MysqlDatabase get() {
- if (database.isClosed()) {
- MusicBot.LOGGER.info("Reconnecting to database..");
- database.reconnect();
- }
-
- return database;
- }
-
- public static void shutdown() {
- try {
- database.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Optional, will be called if the method {@link #get(String) get()} is used
- */
- public static void init() {
- if (controllers.isEmpty())
- loadControllers();
-
- ThreadManager.scheduler.scheduleWithFixedDelay(() -> {
- DBController.get().query("SELECT 1;");
- }, 0, 120, TimeUnit.SECONDS);
- }
-
- /**
- * Loads all the controllers which extends {@link AbstractController}
- */
- private static void loadControllers() {
- MusicBot.LOGGER.info("Loading databases controllers..");
-
- Reflections reflections = new Reflections("netneutrality.db.controllers");
- Set> classes = reflections.getSubTypesOf(AbstractController.class);
-
- for (Class extends AbstractController> s : classes) {
- try {
- if (Modifier.isAbstract(s.getModifiers())) {
- continue;
- }
-
- AbstractController c = s.getConstructor().newInstance();
- c.setDatabase(database);
-
- if (!controllers.containsKey(c.getControllerName())) {
- controllers.put(c.getControllerName(), c);
- MusicBot.LOGGER.debug("Loaded controller: " + c.getControllerName());
- }
-
- } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
- e.printStackTrace();
- }
- }
- }
-
-
-}
diff --git a/src/main/java/netneutrality/db/MysqlConnector.java b/src/main/java/netneutrality/db/MysqlConnector.java
deleted file mode 100644
index 37fadac..0000000
--- a/src/main/java/netneutrality/db/MysqlConnector.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package netneutrality.db;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
-
-public class MysqlConnector {
-
- private String dbName;
- private String dbUser;
- private String dbHost;
- private String dbPass;
- private Connection c;
-
- MysqlConnector(String dbName, String dbUser, String dbHost, String dbPass) {
- this.dbName = dbName;
- this.dbUser = dbUser;
- this.dbHost = dbHost;
- this.dbPass = dbPass;
- }
-
- /**
- * Connect to a MySQL instance using the provided informations
- *
- * @return {@link MysqlConnector}
- * @throws SQLException
- */
- public MysqlConnector connect() throws SQLException {
- MysqlConnectionPoolDataSource dataSource = new MysqlConnectionPoolDataSource();
- dataSource.setUser(dbUser);
- dataSource.setPassword(dbPass);
- dataSource.setServerName(dbHost);
- dataSource.setPort(3306);
- dataSource.setDatabaseName(dbName);
- dataSource.setAutoReconnect(true);
- dataSource.setAutoReconnectForConnectionPools(true);
- c = dataSource.getConnection();
- return this;
- }
-
- /**
- * Get the MySQL connection. Must call {@link #connect()} beforehand.
- *
- * @return {@link Connection}
- */
- public Connection getConnection() {
- return c;
- }
-
-}
diff --git a/src/main/java/netneutrality/db/MysqlDatabase.java b/src/main/java/netneutrality/db/MysqlDatabase.java
deleted file mode 100644
index b66879f..0000000
--- a/src/main/java/netneutrality/db/MysqlDatabase.java
+++ /dev/null
@@ -1,231 +0,0 @@
-package netneutrality.db;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.sql.Types;
-import java.util.Calendar;
-
-import netneutrality.MusicBot;
-import netneutrality.db.models.MRowModel;
-import netneutrality.db.models.MTableModel;
-import netneutrality.exceptions.UnimplementedParameterException;
-import netneutrality.handler.LogHandler;
-
-public class MysqlDatabase {
-
- private Connection connection;
- private String dbName;
-
- MysqlDatabase(String dbName) {
- this.dbName = dbName;
- try {
- MusicBot.LOGGER.info("Connecting to database...");
- connection = new MysqlConnector(dbName, "root", "localhost", "").connect().getConnection();
- } catch (SQLException e) {
- MusicBot.LOGGER.error("Error connecting to database. Shutting down..");
- MusicBot.disconnect();
- System.exit(0);
- }
- }
-
- /**
- * Execute a simple query without any parameters.
- * Example : SELECT * FROM table
- *
- * @param query The SQL query to execute
- * @return {@link ResultSet}
- */
- public ResultSet query(String query) {
- Statement stmt;
- try {
- stmt = connection.createStatement();
- return stmt.executeQuery(query);
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return null;
- }
-
- /**
- * Execute an UPDATE statement with the specified parameters.
- * In the SQL query, where there is a parameter to be included, you need to replace it with ?
- *
- * @param sql The SQL query to execute
- * @param params The parameters to include in the query
- * @return an integer of how many row were affected
- */
- public int update(String sql, Object... params) {
- return executeUpdate(sql, params);
- }
-
- /**
- * Execute an INSERT statement with the specified parameters.
- * In the SQL query, where there is a parameter to be included, you need to replace it with ?
- *
- * @param sql The SQL query to execute
- * @param params The parameters to include in the query
- * @return an integer of how many row were affected
- */
- public int insert(String sql, Object... params) {
- return executeUpdate(sql, params);
- }
-
- /**
- * Execute a DELETE statement with the specified parameters.
- * In the SQL query, where there is a parameter to be included, you need to replace it with ?
- *
- * @param sql The SQL query to execute
- * @param params The parameters to include in the query
- * @return an integer of how many row were affected
- */
- public int delete(String sql, Object... params) {
- return executeUpdate(sql, params);
- }
-
- private int executeUpdate(String sql, Object[] params) {
- try {
- PreparedStatement query = connection.prepareStatement(sql);
- resolveParameters(query, params);
- return query.executeUpdate();
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return -1;
- }
-
- /**
- * Execute a SELECT statement with the specified parameters.
- * In the SQL query, where there is a parameter to be included, you need to replace it with ?
- *
- * @param sql The SQL query to execute
- * @param params The parameters to include in the query
- * @return a ResultSet object, which can be converted to an model using {@link MysqlDatabase#convertToTableModel(ResultSet)}
- */
- public ResultSet prepareAndExecute(String sql, Object... params) {
- try {
- PreparedStatement query = connection.prepareStatement(sql);
- resolveParameters(query, params);
- return query.executeQuery();
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return null;
- }
-
- /**
- * Insert parameters (specified with a ?) in the SQL statement
- * Taken from https://github.com/Kaaz/DiscordBot/blob/master/src/main/java/emily/db/MySQLAdapter.java#resolveParameters
- *
- * @param query The PreparedStatement object
- * @param params The parameters to be included in the prepared statement
- * @throws SQLException
- */
- private void resolveParameters(PreparedStatement query, Object... params) throws SQLException {
- int index = 1;
- for (Object p : params) {
- if (p instanceof String) {
- query.setString(index, (String) p);
- } else if (p instanceof Integer) {
- query.setInt(index, (int) p);
- } else if (p instanceof Boolean) {
- query.setBoolean(index, (Boolean) p);
- } else if (p instanceof Long) {
- query.setLong(index, (Long) p);
- } else if (p instanceof Double) {
- query.setDouble(index, (double) p);
- } else if (p instanceof java.sql.Date) {
- java.sql.Date d = (java.sql.Date) p;
- query.setDate(index, d);
- } else if (p instanceof java.util.Date) {
- java.util.Date d = (java.util.Date) p;
- Timestamp ts = new Timestamp(d.getTime());
- query.setTimestamp(index, ts);
- } else if (p instanceof Calendar) {
- Calendar cal = (Calendar) p;
- Timestamp ts = new Timestamp(cal.getTimeInMillis());
- query.setTimestamp(index, ts);
- } else if (p == null) {
- query.setNull(index, Types.NULL);
- } else {
- throw new UnimplementedParameterException(p, index);
- }
- index++;
- }
- }
-
- /**
- * Close the MySQL connection
- *
- * @throws SQLException
- */
- public void close() throws SQLException {
- System.out.println("Closing database...");
- connection.close();
- }
-
- /**
- * Recreate and reconnect the MySQL connection.
- */
- public void reconnect() {
-
- try {
- System.out.println("Reconnecting to database...");
- connection = new MysqlConnector(dbName, "root", "localhost", "").connect().getConnection();
- } catch (SQLException e) {
- System.err.println("Error connecting to database. Shutting down..");
- MusicBot.shutdown();
- System.exit(0);
- }
- }
-
- /**
- * Check if the database is closed.
- *
- * @return the connection status
- * @throws SQLException
- */
- public boolean isClosed() {
- try {
- return connection.isClosed();
- } catch (SQLException e) {
- return true;
- }
- }
-
- /**
- * @param rs The ResultSet returned by the SELECT query
- * @return a {@link MTableModel} with the appropriate column and rows
- * @throws SQLException
- */
- public static MTableModel convertToTableModel(ResultSet rs) {
- MTableModel table = new MTableModel();
-
- try {
- ResultSetMetaData metadata = rs.getMetaData();
- for (int i = 1; i < metadata.getColumnCount() + 1; i++) {
- table.addColumn(metadata.getColumnName(i));
- }
-
- while (rs.next()) {
- MRowModel row = new MRowModel();
- for (int i = 0; i < metadata.getColumnCount(); i++) {
- row.addColumnToRow(rs.getObject(i + 1));
- }
- table.addRow(row);
- }
-
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return table;
- }
-}
diff --git a/src/main/java/netneutrality/db/controllers/DBBlacklist.java b/src/main/java/netneutrality/db/controllers/DBBlacklist.java
deleted file mode 100644
index 255b0b6..0000000
--- a/src/main/java/netneutrality/db/controllers/DBBlacklist.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package netneutrality.db.controllers;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-import netneutrality.ThreadManager;
-import netneutrality.db.AbstractController;
-import netneutrality.db.DBController;
-import netneutrality.db.MysqlDatabase;
-import netneutrality.db.models.MBlacklist;
-import netneutrality.db.models.MTableModel;
-
-public class DBBlacklist extends AbstractController {
-
- public DBBlacklist() {
-
- }
-
- /**
- * Returns an instance of {@link MysqlDatabase} with the database name as set in the {@link DBController} class
- *
- * @return {@link MysqlDatabase}
- */
- public MysqlDatabase getDatabase() {
- return database;
- }
-
- public void add(MBlacklist obj) {
- if (!isAlreadyBlacklisted(obj.getWord(), obj.getServerId())) {
- database.insert("INSERT INTO blacklist VALUES(?,?,?,?)", obj.getWord(), obj.getServerId(), obj.isEnabled(), obj.getCreatorId());
- } else {
- throw new RuntimeException("Word is already blacklisted");
- }
-
- }
-
- public void delete(String word, String serverId) {
- if (isAlreadyBlacklisted(word, serverId)) {
- database.delete("DELETE FROM blacklist WHERE word = ? AND serverId = ?", word, serverId);
- } else {
- throw new RuntimeException("Is not blacklisted");
- }
- }
-
- public void enable(String word, String serverId) {
- if (isAlreadyBlacklisted(word, serverId)) {
- database.update("UPDATE blacklist SET isEnabled = 1 WHERE word = ? AND serverId = ?", word, serverId);
- } else {
- throw new RuntimeException("Is not blacklisted");
- }
-
- }
-
- public void disable(String word, String serverId) {
- if (isAlreadyBlacklisted(word, serverId)) {
- database.update("UPDATE blacklist SET isEnabled = 0 WHERE word = ? AND serverId = ?", word, serverId);
- } else {
- throw new RuntimeException("Is not blacklisted");
- }
-
- }
-
- public void getBlacklistForWord(String word, String serverId, Consumer success) {
- ThreadManager.executor.submit(() -> {
- MBlacklist blacklisted = null;
- if (isAlreadyBlacklisted(word, serverId)) {
- ResultSet rs = database.prepareAndExecute("SELECT * FROM blacklist WHERE word = ? AND serverId = ?", word, serverId);
- MTableModel table = database.convertToTableModel(rs);
-
- if (table.getNbRows() > 0) {
- blacklisted = new MBlacklist((String) table.getRow(0).get(0), (Boolean) table.getRow(0).get(2), (String) table.getRow(0).get(3),
- (String) table.getRow(0).get(1));
- }
- } else {
- throw new RuntimeException("Is not blacklisted");
- }
-
- success.accept(blacklisted);
- });
-
- }
-
- public void getBlacklistFromServer(String serverId, Consumer> success) {
- ThreadManager.executor.submit(() -> {
- ArrayList output = new ArrayList<>();
-
- try {
- ResultSet rs = database.prepareAndExecute("SELECT * FROM blacklist WHERE serverId = ? AND isEnabled = 1", serverId);
- MTableModel model = MysqlDatabase.convertToTableModel(rs);
-
- if (model.getNbRows() != 0) {
- for (int row = 0; row < model.getNbRows(); row++) {
- output.add(new MBlacklist((String) model.getRow(row).get(0), (Boolean) model.getRow(row).get(2), (String) model.getRow(row).get(3),
- (String) model.getRow(row).get(1)));
- }
- } else {
- throw new RuntimeException("Blacklist is empty");
- }
-
- } catch (NullPointerException | IndexOutOfBoundsException ignored) { }
-
- success.accept(output);
- });
-
- }
-
- public boolean isAlreadyBlacklisted(String word, String serverId) {
- ResultSet rs = database.prepareAndExecute("SELECT COUNT(*) FROM blacklist WHERE word = ? AND serverId = ?", word, serverId);
- try {
- if (rs.next()) {
- return rs.getInt(1) != 0;
- }
- } catch (SQLException e) {
- //TODO: error log
- e.printStackTrace();
- }
-
- return false;
- }
-
- @Override
- public String getControllerName() {
- return "blacklist";
- }
-}
diff --git a/src/main/java/netneutrality/db/controllers/DBLogging.java b/src/main/java/netneutrality/db/controllers/DBLogging.java
deleted file mode 100644
index d979d09..0000000
--- a/src/main/java/netneutrality/db/controllers/DBLogging.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package netneutrality.db.controllers;
-
-/*
- * Copyright (c) anthorob
- *
- * https://xtremcommunity.com/
- * https://discord.gg/CdFcyQ
- */
-
-import net.dv8tion.jda.core.entities.TextChannel;
-import netneutrality.MusicBot;
-import netneutrality.ThreadManager;
-import netneutrality.db.AbstractController;
-import netneutrality.handler.LogHandler;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.function.Consumer;
-
-public class DBLogging extends AbstractController {
-
- public void getLoggingChannel(String serverId, Consumer success) {
- ThreadManager.executor.submit(() -> {
- TextChannel txt = null;
- ResultSet rs = database.prepareAndExecute("SELECT channelId FROM logging WHERE serverId = ?", serverId);
-
- try {
- if (rs.next()) {
- txt = MusicBot.getJDA().getGuildById(serverId).getTextChannelById(rs.getString(1));
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- success.accept(txt);
- });
-
- }
-
- public void setupLog(String serverId, String channelId) {
- database.insert("INSERT INTO logging VALUES(?,?,?)", serverId, channelId, true);
- }
-
- public void toggleLog(String serverId, String channelId) {
- database.update("UPDATE logging SET enabled = NOT enabled");
- }
-
- public boolean isLoggingEnabled(String serverId, String channelId) {
-
- try {
- ResultSet rs = database.prepareAndExecute("SELECT enabled FROM logging WHERE serverId = ? AND channelId = ?", serverId, channelId);
- if (rs.next()) {
- return rs.getBoolean(1);
- }
-
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return false;
- }
-
- @Override
- public String getControllerName() {
- return "logging";
- }
-}
diff --git a/src/main/java/netneutrality/db/controllers/DBMessage.java b/src/main/java/netneutrality/db/controllers/DBMessage.java
deleted file mode 100644
index a2db481..0000000
--- a/src/main/java/netneutrality/db/controllers/DBMessage.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package netneutrality.db.controllers;
-
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.sql.ResultSet;
-
-import netneutrality.ThreadManager;
-import netneutrality.db.AbstractController;
-import netneutrality.db.DBController;
-import netneutrality.db.MysqlDatabase;
-import netneutrality.db.models.MMessage;
-import netneutrality.db.models.MTableModel;
-import netneutrality.handler.LogHandler;
-
-import javax.swing.table.TableModel;
-
-public class DBMessage extends AbstractController {
-
- public DBMessage() { }
-
- /**
- * Returns an instance of {@link MysqlDatabase} with the database name as set in the {@link DBController} class
- *
- * @return {@link MysqlDatabase}
- */
- public MysqlDatabase getDatabase() {
- return database;
- }
-
- public void log(String servername, String channelname, String username, String datesent, String messageId, String message) {
-
- ThreadManager.executor.submit(() -> {
- database.insert("INSERT INTO log_message VALUES(?,?,?,?,?,?,?)", servername, channelname, username, datesent, messageId, message, null);
- });
-
- }
-
- public void logEdit(String messageId, String newMessage) {
-
- ThreadManager.executor.submit(() -> {
- database.update("UPDATE log_message SET editedMessage = ? WHERE messageId = ?", newMessage, messageId);
- });
-
- }
-
- public MMessage getMessage(String messageId) {
- ResultSet rs = database.prepareAndExecute("SELECT * FROM log_message WHERE messageId = ?", messageId);
- MTableModel table = MysqlDatabase.convertToTableModel(rs);
- if (table.getNbRows() > 0) {
- return new MMessage(table.getRowModel(0).getItem(0), table.getRowModel(0).getItem(1), table.getRowModel(0).getItem(2), table.getRowModel(0).getItem(3), table.getRowModel(0).getItem(4), table.getRowModel(0).getItem(5));
- }
-
-
- return null;
- }
-
- public ArrayList getMessagesFromUser(String username, String serverName, int max) {
- ArrayList messages = new ArrayList<>();
-
- ResultSet rs = database.prepareAndExecute("SELECT * FROM log_message WHERE userName = ? AND serverName = ? ORDER BY dateSent DESC LIMIT ?", username, serverName, max);
- MTableModel table = MysqlDatabase.convertToTableModel(rs);
- if (table.getNbRows() > 0) {
- for (int row = 0; row < max; row++) {
- messages.add(new MMessage(table.getRowModel(row).getItem(0), table.getRowModel(row).getItem(1), table.getRowModel(row).getItem(2), table.getRowModel(row).getItem(3), table.getRowModel(row).getItem(4), table.getRowModel(row).getItem(5)));
- }
- }
-
-
- return messages;
- }
-
- public String getMessageString(String messageId) {
-
- try {
- ResultSet rs = database.prepareAndExecute("SELECT message FROM log_message WHERE messageId = ?", messageId);
- if (rs.next()) {
- return rs.getString(1);
- }
-
- } catch (SQLException e) {
- LogHandler.log(e);
- }
-
- return "No message found";
- }
-
-
-
- @Override
- public String getControllerName() {
- return "message";
- }
-}
diff --git a/src/main/java/netneutrality/db/controllers/DBSettings.java b/src/main/java/netneutrality/db/controllers/DBSettings.java
deleted file mode 100644
index 96a4185..0000000
--- a/src/main/java/netneutrality/db/controllers/DBSettings.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package netneutrality.db.controllers;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.function.Consumer;
-
-import netneutrality.ThreadManager;
-import netneutrality.db.AbstractController;
-import netneutrality.db.MysqlDatabase;
-import netneutrality.db.models.MTableModel;
-
-public class DBSettings extends AbstractController {
-
- public void getSettingsByKey(String key, Consumer