commit 3c88f2e9f25f9c5f480caa3fb4f15fee28348a6f Author: Logan Fick Date: Sun Mar 10 18:28:57 2019 -0400 Initial commit of Redstoner-Bot. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1f3ba6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,147 @@ +# Created by https://www.gitignore.io/api/java,linux,gradle,intellij,redis +# Edit at https://www.gitignore.io/?templates=java,linux,gradle,intellij,redis + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Redis ### +# Ignore redis binary dump (dump.rdb) files + +*.rdb + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +### Gradle Patch ### +**/build/ + +# End of https://www.gitignore.io/api/java,linux,gradle,intellij,redis diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..69ba39f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,72 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..8cf5692 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3e68e4d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,26 @@ + + + + + + + + + Manifest + + + Spelling + + + + + SpellCheckingInspection + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ab0e50e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/Redstoner-Bot.main.iml b/.idea/modules/Redstoner-Bot.main.iml new file mode 100644 index 0000000..98e6162 --- /dev/null +++ b/.idea/modules/Redstoner-Bot.main.iml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/Redstoner-Bot.test.iml b/.idea/modules/Redstoner-Bot.test.iml new file mode 100644 index 0000000..3cf96ed --- /dev/null +++ b/.idea/modules/Redstoner-Bot.test.iml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e3b1ed6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: java + +jdk: + - openjdk8 + - oraclejdk8 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3bbbc1e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + \ No newline at end of file diff --git a/Redstoner-Bot.iml b/Redstoner-Bot.iml new file mode 100644 index 0000000..85d80a9 --- /dev/null +++ b/Redstoner-Bot.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c616ff2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.2' + } +} + +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'java' + +repositories { + jcenter() + + maven { + url 'https://jitpack.io' + } +} + +dependencies { + implementation 'net.dv8tion:JDA:3.8.3_460' + implementation 'com.github.LogalDeveloper:LogalBot:f4f13ddc45' + implementation 'mysql:mysql-connector-java:5.1.37' +} + +jar { + manifest { + attributes "Main-Class": "com.redstoner.discordbot.Main" + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..28861d2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..115e6ac --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9edecdb --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'Redstoner-Bot' \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/Main.java b/src/main/java/com/redstoner/discordbot/Main.java new file mode 100644 index 0000000..3ba20da --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/Main.java @@ -0,0 +1,59 @@ +package com.redstoner.discordbot; + +import com.redstoner.discordbot.commands.LinkAccount; +import com.redstoner.discordbot.commands.LinkStatus; +import com.redstoner.discordbot.commands.Nickname; +import com.redstoner.discordbot.tasks.ForumNotificationTask; +import com.redstoner.discordbot.tasks.GuildRankSyncTask; +import com.redstoner.discordbot.utils.RankUtil; +import dev.logal.logalbot.LogalBot; +import dev.logal.logalbot.commands.CommandManager; +import dev.logal.logalbot.utils.Scheduler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public final class Main { + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + public static void main(String[] arguments) { + logger.info("Beginning startup of Redstoner-Bot..."); + RankUtil.registerRank("visitor", "470250026925293568"); + RankUtil.registerRank("member", "470233551284076555"); + RankUtil.registerRank("builder", "470233521919885317"); + RankUtil.registerRank("trusted", "470233420501352469"); + RankUtil.registerRank("helper", "518547428690100227"); + RankUtil.registerRank("trainingmod", "470233311541985320"); + RankUtil.registerRank("mod", "470233311541985320"); + RankUtil.registerRank("admin", "470233150514003969"); + + RankUtil.registerRank("+lead", "516775853976453140"); + RankUtil.registerRank("+donor", "470237589052325888"); + RankUtil.registerRank("+donorplus", "470258077648683008"); + RankUtil.registerRank("+retired", "537816902584631301"); + RankUtil.registerRank("+dev", "508329092110614529"); + + logger.info("Starting LogalBot..."); + LogalBot.start(false); + + logger.info("Unregistering unnecessary commands..."); + CommandManager.unregisterCommand("help"); + CommandManager.unregisterCommand("about"); + + logger.info("Registering Redstoner commands..."); + CommandManager.registerCommand("linkaccount", new LinkAccount(), false); + CommandManager.registerCommandAlias("link", "linkaccount"); + CommandManager.registerCommandAlias("lnk", "linkaccount"); + CommandManager.registerCommand("nickname", new Nickname(), true); + CommandManager.registerCommandAlias("nick", "nickname"); + CommandManager.registerCommand("linkstatus", new LinkStatus(), false); + CommandManager.registerCommandAlias("ls", "linkstatus"); + + logger.info("Starting tasks..."); + Scheduler.scheduleAtFixedRate(new ForumNotificationTask(), 1, 1, TimeUnit.MINUTES); + Scheduler.scheduleAtFixedRate(new GuildRankSyncTask(), 1, 5, TimeUnit.MINUTES); + + logger.info("Redstoner-Bot setup complete."); + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/commands/LinkAccount.java b/src/main/java/com/redstoner/discordbot/commands/LinkAccount.java new file mode 100644 index 0000000..ac18069 --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/commands/LinkAccount.java @@ -0,0 +1,93 @@ +package com.redstoner.discordbot.commands; + +import com.redstoner.discordbot.utils.RankUtil; +import dev.logal.logalbot.commands.Command; +import dev.logal.logalbot.commands.CommandResponse; +import dev.logal.logalbot.utils.DataManager; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.TextChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public final class LinkAccount implements Command { + private static final Logger logger = LoggerFactory.getLogger(LinkAccount.class); + + private final String databaseUsername = System.getenv("MYSQL_USERNAME"); + private final String databasePassword = System.getenv("MYSQL_PASSWORD"); + private final String tokenDatabaseName = System.getenv("MYSQL_TOKEN_DATABASE_NAME"); + + @Override + public CommandResponse execute(String[] arguments, Member executor, TextChannel channel) { + if (DataManager.getUserValue(executor, "minecraftUUID") != null) { + return new CommandResponse("no_entry_sign", "Sorry " + executor.getAsMention() + ", but your account is already linked.").setDeletionDelay( + 10, TimeUnit.SECONDS); + } + + String token = arguments[0]; + + if (token.length() != 8 || !token.matches("[A-Za-z0-9]+")) { + return new CommandResponse("no_entry_sign", "Sorry " + executor.getAsMention() + ", but that doesn't appear to be a token.").setDeletionDelay( + 10, TimeUnit.SECONDS); + } + + UUID uuid; + try { + Class.forName("com.mysql.jdbc.Driver"); + Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + tokenDatabaseName, databaseUsername, databasePassword); + + Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery("SELECT uuid FROM discord WHERE token='" + token + "' AND used=0"); + + if (!results.next()) { + return new CommandResponse( + "no_entry_sign", "Sorry " + executor.getAsMention() + ", but that token doesn't appear to be valid.").setDeletionDelay( + 10, TimeUnit.SECONDS); + } + + uuid = UUID.fromString(UUID + .fromString(results + .getString(1) + .replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", + "$1-$2-$3-$4-$5" + )) + .toString()); + + statement = connection.createStatement(); + statement.execute("UPDATE discord SET used=1 WHERE token='" + token + "'"); + + connection.close(); + } catch (Throwable exception) { + logger.error( + "An error occurred while trying to look up the token for " + executor.getEffectiveName() + " (" + executor.getUser().getId() + ")!", + exception + ); + return new CommandResponse( + "sos", "Sorry " + executor.getAsMention() + ", but an error occurred while trying to look up your token.").setDeletionDelay( + 10, TimeUnit.SECONDS); + } + + DataManager.setUserValue(executor, "minecraftUUID", uuid.toString()); + try { + RankUtil.syncRanks(executor); + } catch (Throwable exception) { + logger.error( + "An error occurred while trying to sync the ranks for " + executor.getEffectiveName() + " (" + executor.getUser().getId() + ")!", + exception + ); + return new CommandResponse( + "exclamation", + executor.getAsMention() + ", your account has been successfully linked, but your ranks could not be synchronized at this time." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + + return new CommandResponse("link", executor.getAsMention() + ", your account has been successfully linked and your ranks have been synchronized."); + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/commands/LinkStatus.java b/src/main/java/com/redstoner/discordbot/commands/LinkStatus.java new file mode 100644 index 0000000..3b7734a --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/commands/LinkStatus.java @@ -0,0 +1,18 @@ +package com.redstoner.discordbot.commands; + +import dev.logal.logalbot.commands.Command; +import dev.logal.logalbot.commands.CommandResponse; +import dev.logal.logalbot.utils.DataManager; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.TextChannel; + +public final class LinkStatus implements Command { + @Override + public CommandResponse execute(String[] arguments, Member executor, TextChannel channel) { + if (DataManager.getUserValue(executor, "minecraftUUID") == null) { + return new CommandResponse("negative_squared_cross_mark", executor.getAsMention() + ", your account is not linked."); + } else { + return new CommandResponse("ballot_box_with_check", executor.getAsMention() + ", your account is linked."); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/commands/Nickname.java b/src/main/java/com/redstoner/discordbot/commands/Nickname.java new file mode 100644 index 0000000..e4ca05a --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/commands/Nickname.java @@ -0,0 +1,96 @@ +package com.redstoner.discordbot.commands; + +import com.redstoner.discordbot.utils.RankUtil; +import dev.logal.logalbot.commands.Command; +import dev.logal.logalbot.commands.CommandResponse; +import dev.logal.logalbot.utils.DataManager; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.TextChannel; + +import java.util.concurrent.TimeUnit; + +public final class Nickname implements Command { + @Override + public CommandResponse execute(String[] arguments, Member executor, TextChannel channel) { + Guild guild = channel.getGuild(); + if (arguments.length == 0) { + if (DataManager.getUserValue(executor, "minecraftUUID") == null) { + return new CommandResponse( + "no_entry_sign", + "Sorry " + executor.getAsMention() + ", but your nickname could not be reset because your account is not linked." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } else { + DataManager.setUserValue(executor, "nickname", ""); + try { + RankUtil.syncRanks(executor); + return new CommandResponse("white_check_mark", executor.getAsMention() + ", your nickname has been reset."); + } catch (Throwable exception) { + return new CommandResponse( + "sos", + "Sorry " + executor.getAsMention() + ", but your nickname could not be reset because an error occurred while trying to look up your name." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + } + } + + String userID = arguments[0].replaceFirst("<@[!]?([0-9]*)>", "$1"); + Member target; + try { + target = guild.getMemberById(userID); + } catch (Throwable exception) { + target = null; + } + + if (target == null) { + if (DataManager.getUserValue(executor, "minecraftUUID") == null) { + return new CommandResponse( + "no_entry_sign", + "Sorry " + executor.getAsMention() + ", but your nickname could not be reset because your account is not linked." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } else { + DataManager.setUserValue(executor, "nickname", arguments[0]); + try { + RankUtil.syncRanks(executor); + return new CommandResponse("white_check_mark", executor.getAsMention() + ", your nickname has been set."); + } catch (Throwable exception) { + return new CommandResponse( + "sos", + "Sorry " + executor.getAsMention() + ", but your nickname could not be set because an error occurred while trying to look up your name." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + } + } else { + if (DataManager.getUserValue(target, "minecraftUUID") == null) { + return new CommandResponse( + "no_entry_sign", + "Sorry " + executor.getAsMention() + ", but the nickname for that user could not be set because their account is not linked." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + + if (arguments.length == 1) { + try { + DataManager.setUserValue(target, "nickname", ""); + RankUtil.syncRanks(target); + return new CommandResponse("white_check_mark", executor.getAsMention() + " has reset the nickname for " + target.getAsMention() + "."); + } catch (Throwable exception) { + return new CommandResponse( + "sos", + "Sorry " + executor.getAsMention() + ", but the nickname for that user could not be reset because an error occurred while trying to look up their name." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + } else { + try { + DataManager.setUserValue(target, "nickname", arguments[1]); + RankUtil.syncRanks(target); + return new CommandResponse("white_check_mark", executor.getAsMention() + " has set the nickname for " + target.getAsMention() + "."); + } catch (Throwable exception) { + return new CommandResponse( + "sos", + "Sorry " + executor.getAsMention() + ", but the nickname for that user could not be set because an error occurred while trying to look up their name." + ).setDeletionDelay(10, TimeUnit.SECONDS); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/tasks/ForumNotificationTask.java b/src/main/java/com/redstoner/discordbot/tasks/ForumNotificationTask.java new file mode 100644 index 0000000..4ff5f77 --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/tasks/ForumNotificationTask.java @@ -0,0 +1,178 @@ +package com.redstoner.discordbot.tasks; + +import dev.logal.logalbot.LogalBot; +import dev.logal.logalbot.utils.DataManager; +import dev.logal.logalbot.utils.StringUtil; +import net.dv8tion.jda.core.entities.Guild; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.InputStream; +import java.net.URL; + +public final class ForumNotificationTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(ForumNotificationTask.class); + + private static final Guild redstonerGuild = LogalBot.getJDA().getGuildById("470229570310766593"); + + private static final String announcementsChannel = "489957437978181635"; + private static final String staffGeneralChannel = "472452678367313941"; + private static final String staffDevelopmentChannel = "491050574092042241"; + + @Override + public void run() { + try { + // Blog posts + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream stream = new URL("https://redstoner.com/blog.atom").openStream(); + Document doc = docBuilder.parse(stream); + + Element topEntry = (Element) doc.getDocumentElement().getElementsByTagName("entry").item(0); + + String title = topEntry.getElementsByTagName("title").item(0).getFirstChild().getNodeValue(); + + Element authorBlock = (Element) topEntry.getElementsByTagName("author").item(0); + String author = authorBlock.getElementsByTagName("name").item(0).getFirstChild().getNodeValue(); + + String id = topEntry.getElementsByTagName("id").item(0).getFirstChild().getNodeValue(); + String link = topEntry.getElementsByTagName("link").item(0).getAttributes().getNamedItem("href").getNodeValue(); + + if (DataManager.getGuildValue(redstonerGuild, "knownAnnouncement:" + id) == null) { + LogalBot + .getJDA() + .getTextChannelById(announcementsChannel) + .sendMessage( + ":loudspeaker: New blog post **" + StringUtil.sanitize(title) + "** authored by *" + StringUtil.sanitize(author) + "*\n" + link) + .queue(); + DataManager.setGuildValue(redstonerGuild, "knownAnnouncement:" + id, "true"); + } + } catch (Throwable exception) { + logger.error("An error occurred while posting new blog posts to Discord!", exception); + } + + try { + // Blames + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream stream = new URL("https://redstoner.com/forums/21.atom").openStream(); + Document doc = docBuilder.parse(stream); + + Element topEntry = (Element) doc.getDocumentElement().getElementsByTagName("entry").item(0); + + String title = topEntry.getElementsByTagName("title").item(0).getFirstChild().getNodeValue(); + + Element authorBlock = (Element) topEntry.getElementsByTagName("author").item(0); + String author = authorBlock.getElementsByTagName("name").item(0).getFirstChild().getNodeValue(); + + String id = topEntry.getElementsByTagName("id").item(0).getFirstChild().getNodeValue(); + String link = topEntry.getElementsByTagName("link").item(0).getAttributes().getNamedItem("href").getNodeValue(); + + if (DataManager.getGuildValue(redstonerGuild, "knownBlame:" + id) == null) { + LogalBot + .getJDA() + .getTextChannelById(staffGeneralChannel) + .sendMessage( + ":loudspeaker: New blame **" + StringUtil.sanitize(title) + "** authored by *" + StringUtil.sanitize(author) + "*\n" + link) + .queue(); + DataManager.setGuildValue(redstonerGuild, "knownBlame:" + id, "true"); + } + } catch (Throwable exception) { + logger.error("An error occurred while posting new blames to Discord!", exception); + } + + try { + // Appeals + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream stream = new URL("https://redstoner.com/forums/5.atom").openStream(); + Document doc = docBuilder.parse(stream); + + Element topEntry = (Element) doc.getDocumentElement().getElementsByTagName("entry").item(0); + + String title = topEntry.getElementsByTagName("title").item(0).getFirstChild().getNodeValue(); + + Element authorBlock = (Element) topEntry.getElementsByTagName("author").item(0); + String author = authorBlock.getElementsByTagName("name").item(0).getFirstChild().getNodeValue(); + + String id = topEntry.getElementsByTagName("id").item(0).getFirstChild().getNodeValue(); + String link = topEntry.getElementsByTagName("link").item(0).getAttributes().getNamedItem("href").getNodeValue(); + + if (DataManager.getGuildValue(redstonerGuild, "knownAppeal:" + id) == null) { + LogalBot + .getJDA() + .getTextChannelById(staffGeneralChannel) + .sendMessage( + ":loudspeaker: New appeal **" + StringUtil.sanitize(title) + "** authored by *" + StringUtil.sanitize(author) + "*\n" + link) + .queue(); + DataManager.setGuildValue(redstonerGuild, "knownAppeal:" + id, "true"); + } + } catch (Throwable exception) { + logger.error("An error occurred while posting new appeals to Discord!", exception); + } + + try { + // Problems & Bugs + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream stream = new URL("https://redstoner.com/forums/11.atom").openStream(); + Document doc = docBuilder.parse(stream); + + Element topEntry = (Element) doc.getDocumentElement().getElementsByTagName("entry").item(0); + + String title = topEntry.getElementsByTagName("title").item(0).getFirstChild().getNodeValue(); + + Element authorBlock = (Element) topEntry.getElementsByTagName("author").item(0); + String author = authorBlock.getElementsByTagName("name").item(0).getFirstChild().getNodeValue(); + + String id = topEntry.getElementsByTagName("id").item(0).getFirstChild().getNodeValue(); + String link = topEntry.getElementsByTagName("link").item(0).getAttributes().getNamedItem("href").getNodeValue(); + + if (DataManager.getGuildValue(redstonerGuild, "knownBugReport:" + id) == null) { + LogalBot + .getJDA() + .getTextChannelById(staffDevelopmentChannel) + .sendMessage(":loudspeaker: New bug report **" + StringUtil.sanitize(title) + "** authored by *" + StringUtil.sanitize( + author) + "*\n" + link) + .queue(); + DataManager.setGuildValue(redstonerGuild, "knownBugReport:" + id, "true"); + } + } catch (Throwable exception) { + logger.error("An error occurred while posting new bug reports to Discord!", exception); + } + + try { + // Website Problems & Bugs + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + InputStream stream = new URL("https://redstoner.com/forums/4.atom").openStream(); + Document doc = docBuilder.parse(stream); + + Element topEntry = (Element) doc.getDocumentElement().getElementsByTagName("entry").item(0); + + String title = topEntry.getElementsByTagName("title").item(0).getFirstChild().getNodeValue(); + + Element authorBlock = (Element) topEntry.getElementsByTagName("author").item(0); + String author = authorBlock.getElementsByTagName("name").item(0).getFirstChild().getNodeValue(); + + String id = topEntry.getElementsByTagName("id").item(0).getFirstChild().getNodeValue(); + String link = topEntry.getElementsByTagName("link").item(0).getAttributes().getNamedItem("href").getNodeValue(); + + if (DataManager.getGuildValue(redstonerGuild, "knownWebsiteBugReport:" + id) == null) { + LogalBot + .getJDA() + .getTextChannelById(staffDevelopmentChannel) + .sendMessage(":loudspeaker: New website bug report **" + StringUtil.sanitize(title) + "** authored by *" + StringUtil.sanitize( + author) + "*\n" + link) + .queue(); + DataManager.setGuildValue(redstonerGuild, "knownWebsiteBugReport:" + id, "true"); + } + } catch (Throwable exception) { + logger.error("An error occurred while posting new website bug reports to Discord!", exception); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/tasks/GuildRankSyncTask.java b/src/main/java/com/redstoner/discordbot/tasks/GuildRankSyncTask.java new file mode 100644 index 0000000..260bcee --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/tasks/GuildRankSyncTask.java @@ -0,0 +1,30 @@ +package com.redstoner.discordbot.tasks; + +import com.redstoner.discordbot.utils.RankUtil; +import dev.logal.logalbot.LogalBot; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class GuildRankSyncTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(GuildRankSyncTask.class); + + private static final String redstonerGuild = "470229570310766593"; + + @Override + public void run() { + Guild guild = LogalBot.getJDA().getGuildById(redstonerGuild); + + for (Member member : guild.getMembers()) { + try { + RankUtil.syncRanks(member); + } catch (Throwable exception) { + logger.error( + "An error occurred while trying to sync the ranks for " + member.getUser().getName() + " (" + member.getUser().getId() + ")!", + exception + ); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redstoner/discordbot/utils/RankUtil.java b/src/main/java/com/redstoner/discordbot/utils/RankUtil.java new file mode 100644 index 0000000..f264f55 --- /dev/null +++ b/src/main/java/com/redstoner/discordbot/utils/RankUtil.java @@ -0,0 +1,87 @@ +package com.redstoner.discordbot.utils; + +import dev.logal.logalbot.utils.DataManager; +import net.dv8tion.jda.core.entities.Guild; +import net.dv8tion.jda.core.entities.Member; +import net.dv8tion.jda.core.entities.Role; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.*; +import java.util.ArrayList; +import java.util.HashMap; + +public final class RankUtil { + private static final Logger logger = LoggerFactory.getLogger(RankUtil.class); + + private static final String databaseUsername = System.getenv("MYSQL_USERNAME"); + private static final String databasePassword = System.getenv("MYSQL_PASSWORD"); + private static final String pexDatabaseName = System.getenv("MYSQL_PEX_DATABASE_NAME"); + + private static final HashMap roleDictionary = new HashMap<>(); + + public static void registerRank(String rankName, String roleID) { + roleDictionary.put(rankName, roleID); + } + + public static void syncRanks(Member member) throws SQLException, ClassNotFoundException { + String uuid = DataManager.getUserValue(member, "minecraftUUID"); + if (uuid == null) { + return; + } + + Class.forName("com.mysql.jdbc.Driver"); + Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/" + pexDatabaseName, databaseUsername, databasePassword); + + Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery("SELECT parent FROM permissions_inheritance WHERE child='" + uuid + "'"); + + Guild guild = member.getGuild(); + ArrayList rolesToAdd = new ArrayList<>(); + while (results.next()) { + String parent = results.getString(1); + Role role = null; + if (roleDictionary.containsKey(parent)) { + role = guild.getRoleById(roleDictionary.get(parent)); + } + + if (role != null) { + rolesToAdd.add(role); + } + } + + String nickname = DataManager.getUserValue(member, "nickname"); + if (nickname != null && !nickname.equals("")) { + guild.getController().setNickname(member, nickname).reason("Redstoner Name Synchronization").queue(); + } else { + statement = connection.createStatement(); + results = statement.executeQuery("SELECT value FROM permissions WHERE type=1 AND permission='name' AND name='" + uuid + "'"); + + if (!results.next()) { + logger.warn(member.getUser().getName() + " (" + member + .getUser() + .getId() + ") has a linked account, but has no name according to PermissionsEx. Leaving Discord nickname as is."); + } else { + String name = results.getString(1); + if (!member.getEffectiveName().equals(name)) { + guild.getController().setNickname(member, name).reason("Redstoner Name Synchronization").queue(); + } + } + } + + connection.close(); + + if (rolesToAdd.size() == 0) { + logger.warn(member.getUser().getName() + " (" + member + .getUser() + .getId() + ") has a linked account, but has no ranks according to PermissionsEx. Assuming they are visitor rank."); + rolesToAdd.add(guild.getRoleById(roleDictionary.get("visitor"))); + } + + if (!CollectionUtils.isEqualCollection(member.getRoles(), rolesToAdd)) { + logger.info("Updating roles for " + member.getUser().getName() + " (" + member.getUser().getId() + ")."); + guild.getController().modifyMemberRoles(member, rolesToAdd).reason("Redstoner Rank Synchronization").queue(); + } + } +} \ No newline at end of file