Merge branch 'dev' into 'master'

Release 2.x

See merge request anthorob/netneutrality!1
This commit is contained in:
anthorob committed 2024-07-15 02:18:42 +00:00
commit 5fb29ea4ed
172 files changed
+8020 -4511

No files matched your search

+13
View File
@@ -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"
]
+13
View File
@@ -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=
+8
View File
@@ -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
+43 -9
View File
@@ -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"
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaPluginProjectSettings">
<option name="lastSelectedLanguage" value="Java" />
</component>
</project>
+18
View File
@@ -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
+29
View File
@@ -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
+25
View File
@@ -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"]
+7 -7
View File
@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
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
<program> Copyright (C) <year> <name of author>
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
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
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
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+15
View File
@@ -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.
+13
View File
@@ -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=
+11
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
./mvnw versions:commit
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
./mvnw versions:set -DnewVersion=$CZ_PRE_NEW_VERSION
+64
View File
@@ -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:
+43
View File
@@ -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:
Binary file not shown.
Vendored Executable
+308
View File
@@ -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 "$@"
Vendored
+205
View File
@@ -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%
+171 -68
View File
@@ -1,84 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xtremcommunity.discord</groupId>
<artifactId>netneutrality</artifactId>
<version>0.0.1</version>
<name>MusicBot</name>
<description>Music bot</description>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<groupId>me.bobert</groupId>
<artifactId>netneutrality</artifactId>
<version>2.0.1</version>
<name>netneutrality-discord-bot</name>
<description>Discord bot</description>
<packaging>jar</packaging>
<distributionManagement>
<downloadUrl>https://gitlab.xtremcommunity.com/nn.jar</downloadUrl>
</distributionManagement>
<properties>
<main.class>me.bobert.netneutrality.NetNeutrality</main.class>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<repositories>
<repository> <!-- This repo fixes issues with transitive dependencies -->
<id>jcenter</id>
<url>http://jcenter.bintray.com</url>
</repository>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</repositories>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<propertiesEncoding>UTF-8</propertiesEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>netneutrality</finalName>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>3.5.0_327</version>
</dependency>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.1</version>
</dependency>
<repository>
<id>jitpack</id>
<url>https://jitpack.io</url>
</repository>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
<repository>
<id>arbjergDev-releases</id>
<name>Lavalink Repository</name>
<url>https://maven.lavalink.dev/releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>dev.arbjerg</groupId>
<artifactId>lavaplayer</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>dev.lavalink.youtube</groupId>
<artifactId>v2</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.1.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-hikaricp</artifactId>
<version>6.1.7.Final</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry</artifactId>
<version>7.6.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.17</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sedmelluq</groupId>
<artifactId>lavaplayer</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<version>4.13.2</version>
</dependency>
</dependencies>
</project>
+163
View File
@@ -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 */;
@@ -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();
}
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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<String, AbstractCommand> 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<Class<? extends AbstractCommand>> 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<CommandData> 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();
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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<OBlacklist> 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();
}
}
@@ -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<MessageEmbed> 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<MessageEmbed> help = MessageFunction.buildHelpMessage(context.getGuildUser());
context.sendMessageInPrivate(help);
return CommandExecutionResult.success("Help has been sent in DM!");
}
}
@@ -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()));
}
}
@@ -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<Role> 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<TextChannel> 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 <value between 0 and 100>'");
}
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<String, String> 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();
}
}
@@ -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();
}
}
@@ -0,0 +1 @@
*.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?");
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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<String, GuildManager> 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();
}
}
}
@@ -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());
});
}
}
@@ -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<String, String> settingsCache = new HashMap<>();
public GuildConfigManager(Guild guild, OGuild guildEntity) {
this.guild = guild;
this.guildEntity = guildEntity;
reloadSettingsCache();
}
public Map<String, String> 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<OGuildSetting> settingSet = guildEntity.getGuildSettings();
for (OGuildSetting setting : settingSet) {
settingsCache.put(setting.getName(), setting.getValue());
}
});
}
}
@@ -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<String, GuildManager> managers = new ConcurrentHashMap<>();
private static final SessionFactory sessionFactory = SessionManager.getSessionFactory();
private final Set<String> 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<String, GuildManager> getManagers() {
return managers;
}
public Guild getGuild() {
return guild;
}
public OGuild getGuildEntity() {
return guildEntity;
}
public Set<String> 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<OBlacklist> 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());
}
}
@@ -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));
}
}
}
@@ -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<String, Object> 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);
}
}
}
@@ -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));
}
}
@@ -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());
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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> T get(Class<T> 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();
}
}
@@ -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;
}
}
@@ -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<OBlacklist> getBlacklistForGuild(Guild guild) {
GuildManager guildManager = GuildManager.getManager(guild);
Session session = getSessionFactory().openSession();
OGuild guildEntity = guildManager.getGuildEntity();
JpaUtils.tryLoad(session, guildEntity, guildEntity.getGuildId());
ArrayList<OBlacklist> blacklists = new ArrayList<>(guildEntity.getBlacklists());
session.close();
return blacklists;
}
private OBlacklist getWordFromDb(String word, String guildId) {
Session session = getSessionFactory().openSession();
Query<OBlacklist> query = session.createNamedQuery("OBlacklist.findByWordEqualsIgnoreCaseAndGuild_GuildIdEquals", OBlacklist.class);
query.setParameter("guildId", guildId);
query.setParameter("word", word);
OBlacklist blacklist = query.uniqueResult();
session.close();
return blacklist;
}
}
@@ -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<OLogMessage> 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<OLogMessage> query = session.createNamedQuery("OLogMessage.findByMessageIdEquals", OLogMessage.class);
query.setParameter("messageId", messageId);
OLogMessage message = query.uniqueResult();
session.close();
return message;
}
public List<OLogMessage> getMessagesFromUser(String userId, String guildId, int max) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> query = session.createNamedQuery("OLogMessage.findByUserIdEquals", OLogMessage.class);
query.setParameter("guildId", guildId);
query.setParameter("userId", userId);
query.setMaxResults(max);
List<OLogMessage> messages = query.getResultList();
session.close();
return messages;
}
public String getMessageString(String messageId) {
Session session = getSessionFactory().openSession();
Query<OLogMessage> 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();
}
}
@@ -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;
}
}
@@ -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<OGuildSetting> guildSettings = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OBlacklist> blacklists = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OLogMessage> logMessages = new LinkedHashSet<>();
@OneToMany(mappedBy = "guild")
private Set<OLogging> OLoggings = new LinkedHashSet<>();
public Set<OLogging> getLoggings() {
return OLoggings;
}
public void setLoggings(Set<OLogging> OLoggings) {
this.OLoggings = OLoggings;
}
public Set<OLogMessage> getLogMessages() {
return logMessages;
}
public void setLogMessages(Set<OLogMessage> logMessages) {
this.logMessages = logMessages;
}
public Set<OBlacklist> getBlacklists() {
return blacklists;
}
public void setBlacklists(Set<OBlacklist> blacklists) {
this.blacklists = blacklists;
}
public Set<OGuildSetting> getGuildSettings() {
return guildSettings;
}
public void setGuildSettings(Set<OGuildSetting> 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<OGuildSetting> setting = getGuildSettings()
.stream()
.filter(setting1 -> setting1.getName().equalsIgnoreCase(name))
.findFirst();
return setting.orElse(null);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
}
}
}
}
@@ -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();
}
}
}
@@ -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) {
}
}
@@ -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);
}
}
@@ -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, [email protected])", 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<String, String> headers) throws IOException {
public WebModule request(Map<String, String> 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<String, String> 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<String, String> params, Map<String, String> headers) throws IOException {
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), createJson(params));
public WebModule post(TreeMap<String, String> params, Map<String, String> 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<String, String> 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<String, String> params) {
StringBuilder json = new StringBuilder();
for (Map.Entry<String, String> 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<String> key, List<String> 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<String> list) {
StringBuilder json = new StringBuilder();
@@ -110,36 +147,43 @@ 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;
}
}
@@ -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();
}
}
@@ -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<String, String> 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<String, String> 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<String, String> 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);
}
}
@@ -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);
}
}
@@ -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 = "<h1>Server start success if you see this message</h1>";
sendStringResponse(exchange, response);
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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<String, MusicManager> 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;
}
}
@@ -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<String> 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<String> 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<TrackInfo> queue;
public QueueManager(TrackScheduler scheduler) {
this.scheduler = scheduler;
this.player = scheduler.getPlayer();
this.guild = scheduler.getGuild();
this.queue = new LinkedBlockingDeque<>();
}
public List<TrackInfo> 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<TrackInfo> 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<AudioTrack> getAllQueuedTrack() {
synchronized (queue) {
ArrayList<AudioTrack> 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<TrackInfo> 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<TrackInfo> 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();
}
}
}
@@ -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;
}
}
@@ -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())));
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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));
}
}
}
@@ -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;
}
}
@@ -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<Member> members = channel.getMembers().stream().filter(member -> !member.getId().equals(NetNeutrality.getSelf().getId())).toList();
return members.isEmpty() || manager.getScheduler().getQueueManager().isEmpty();
}
}
@@ -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();
}
}
}
@@ -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<String, ExecutorService> 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!");
}
}
@@ -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<GuildMessageChannel, ChannelMessagePurger> 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<Message> 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<Message> 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();
}
}
}
@@ -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.
* <p>
* 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.
* <p>
* 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<Session> 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<Session> consumer) {
Session session = SessionManager.getSessionFactory().openSession();
tryLoad(session, persistantEntity, id);
consumer.accept(session);
session.close();
}
}
@@ -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<AudioTrack> 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<AudioTrack> 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<QueueManager.TrackInfo> 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<MessageEmbed> buildHelpMessage(Member member) {
HashMap<CommandType, EmbedBuilder> 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<MessageEmbed> 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<String> 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();
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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<String, String> envs = System.getenv();
for (Map.Entry<String, String> 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");
}
}
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff. Show more