commit 423e9269ee332e10db7fddcbccfa11d2504250c9 Author: Julius de Jeu Date: Thu Sep 3 15:41:55 2020 +0200 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d98cfcf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.idea/ +.gradle/ +build/ +out/ +test.db +gradle/ +gradlew +gradlew.bat +.gitignore \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c386186 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.gradle/ +build/ +out/ +test.db +dump/ +local.properties \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..881ea44 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,27 @@ +image: docker:latest +services: + - docker:dind + +stages: + - build + +variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:latest + NAME: $CI_PROJECT_NAME + DOCKER_BUILDKIT: 1 + +build_and_push: + stage: build + only: + - master + script: + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.voidcorp.nl + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + +only_build: + stage: build + except: + - master + script: + - docker build -t $IMAGE_TAG . diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..22ddcbf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM gradle:6.6-jdk14 AS builder + +COPY --chown=gradle:gradle . /home/gradle/src +WORKDIR /home/gradle/src +RUN gradle distTar + +FROM adoptopenjdk:14-jre-hotspot +COPY --from=builder /home/gradle/src/build/distributions/ottobot.tar /app/ottobot.tar +WORKDIR /app +RUN tar -xvf ottobot.tar +WORKDIR /app/ottobot +CMD bin/ottobot \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..d17e9c0 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,46 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + application + kotlin("jvm") version "1.4.0" +} +group = "nl.voidcorp.ottobot" +version = "1.0-SNAPSHOT" + +repositories { + jcenter() +} + +fun DependencyHandlerScope.exposed(part: String) = implementation("org.jetbrains.exposed", part, "0.27.1") + + +dependencies { + implementation("net.dv8tion:JDA:4.2.0_200") { + exclude(module = "opus-java") + } + implementation("io.ktor:ktor-client-apache:1.4.0") + implementation("io.ktor:ktor-client-jackson:1.4.0") + + implementation("ch.qos.logback:logback-classic:1.2.3") + exposed("exposed-core") + exposed("exposed-dao") + exposed("exposed-jdbc") + implementation("org.xerial:sqlite-jdbc:3.30.1") + implementation("com.impossibl.pgjdbc-ng:pgjdbc-ng:0.8.3") + implementation(kotlin("reflect")) +} + +tasks.withType() { + kotlinOptions.jvmTarget = "13" +} + +tasks { + distTar { + archiveFileName.set("ottobot.tar") + } +} + +application { + applicationName = "ottobot" + mainClassName = "nl.voidcorp.ottobot.MainKt" +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..490fda8 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..a4b4429 --- /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-6.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed 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 +# +# https://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. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..62bd9b9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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.kts b/settings.gradle.kts new file mode 100644 index 0000000..ec57e6a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ + +rootProject.name = "ottobot-v3" + diff --git a/src/main/kotlin/nl/voidcorp/ottobot/Main.kt b/src/main/kotlin/nl/voidcorp/ottobot/Main.kt new file mode 100644 index 0000000..49ff8ac --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/Main.kt @@ -0,0 +1,55 @@ +package nl.voidcorp.ottobot + +import net.dv8tion.jda.api.entities.Activity +import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder +import net.dv8tion.jda.api.sharding.ShardManager +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.database.* +import nl.voidcorp.ottobot.events.CommandListener +import nl.voidcorp.ottobot.events.DisconnectListenerAdapter +import nl.voidcorp.ottobot.events.OttoListener +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.jetbrains.exposed.sql.transactions.transaction +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.sql.Connection + +val logger: Logger = LoggerFactory.getLogger("OttoBot") +const val creator = 168743656738521088 +lateinit var jda: ShardManager + +fun main() { + val token: String = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("Missing DISCORD_TOKEN in env!") + Database.connect( + System.getenv("JDBC_URL") ?: "jdbc:sqlite:test.db", + System.getenv("JDBC_DRIVER") ?: "org.sqlite.JDBC", + System.getenv("JDBC_USER") ?: "", + System.getenv("JDBC_PASSWORD") ?: "" + ) + TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE + transaction { + SchemaUtils.createMissingTablesAndColumns(GuildStores) + SchemaUtils.createMissingTablesAndColumns( + GuildStoreAdminRoles, + GuildStoreBotChannels, + GuildStoreModeratorRoles, + GuildStoreRoleMap + ) + } + startBot(token) +} + +fun startBot(token: String) { + + val builder = DefaultShardManagerBuilder.createDefault(token) + + builder.addEventListeners(CommandListener(Command.allCommands), DisconnectListenerAdapter, OttoListener) + jda = builder.build() + jda.setActivityProvider { + Activity.playing("v3.0 ($it)") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/Command.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/Command.kt new file mode 100644 index 0000000..5fbb839 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/Command.kt @@ -0,0 +1,220 @@ +package nl.voidcorp.ottobot.command + +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.ChannelType +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import nl.voidcorp.ottobot.commands.HelpCommand +import nl.voidcorp.ottobot.commands.`fun`.* +import nl.voidcorp.ottobot.commands.debug.DebugCommand +import nl.voidcorp.ottobot.commands.debug.Flex +import nl.voidcorp.ottobot.commands.debug.PermissionLevelCommand +import nl.voidcorp.ottobot.commands.general.ATQCommand +import nl.voidcorp.ottobot.commands.general.SpillKerrieCommand +import nl.voidcorp.ottobot.commands.general.XYProblemCommand +import nl.voidcorp.ottobot.commands.management.* +import nl.voidcorp.ottobot.commands.roles.* +import nl.voidcorp.ottobot.creator +import nl.voidcorp.ottobot.database.GuildStore +import nl.voidcorp.ottobot.database.GuildStores +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.* + + +abstract class Command( + val name: String, + val helpMesage: String = "", + val usage: String = "", + val commandLevel: CommandLevel = CommandLevel.VERIFIED, + val aliases: List = emptyList(), + val location: CommandSource = CommandSource.BOTH, + val group: CommandGroup = CommandGroup.GENERAL, + val allowAnywhere: Boolean = false +) { + + fun getStore(guildId: Long) = transaction { + GuildStore.find { GuildStores.guildId eq guildId } + .firstOrNull() ?: GuildStore.new { this.guildId = guildId } + } + + fun onCommand(event: MessageReceivedEvent, prefix: String): CommandResult { + val starts = + (event.message.contentRaw.drop(prefix.length).trim().split("\\s".toRegex()) + .first() == name) or (aliases.any { + event.message.contentRaw.drop(prefix.length).trim().split("\\s".toRegex()) + .first() == it + }) + return if (!starts) CommandResult.NOPE else when (location) { + CommandSource.PRIVATE -> if (event.channelType == ChannelType.PRIVATE) guildStuff( + event, + event.message.contentRaw.drop(prefix.length).trim() + ) else CommandResult.NOPE + CommandSource.GUILD -> if (event.channelType == ChannelType.TEXT) privateStuff( + event, + event.message.contentRaw.drop(prefix.length).trim() + ) else CommandResult.NOPE + CommandSource.BOTH -> + if (event.channelType == ChannelType.TEXT) guildStuff( + event, + event.message.contentRaw.drop(prefix.length).trim() + ) + else privateStuff(event, event.message.contentRaw.drop(prefix.length).trim()) + } + } + + private fun guildStuff(event: MessageReceivedEvent, str: String) = + when { + !(isBotChannel(event.textChannel) or + isPermOK(CommandLevel.MODERATOR, getLevel(event.member))) -> CommandResult.CHANNEL + isPermOK(commandLevel, getLevel(event.member)) -> try { + handle(CommandMessage(event, translateCommandline(str))) + } catch (e: Exception) { + e.printStackTrace() + CommandResult.ERROR + } + else -> CommandResult.PERMISSIONS + } + + private fun privateStuff(event: MessageReceivedEvent, str: String) = + if (isPermOK(commandLevel, getLevel(event.member))) try { + handle(CommandMessage(event, translateCommandline(str))) + } catch (e: Exception) { + e.printStackTrace() + CommandResult.ERROR + } else { + CommandResult.PERMISSIONS + } + + abstract fun handle(event: CommandMessage): CommandResult + + fun getLevel(member: Member?): CommandLevel { + if (member == null) return CommandLevel.ADMIN + val guildStore = getStore(member.guild.idLong) + return when { + member.user.idLong == creator -> CommandLevel.MAINTAINER + member.hasPermission(Permission.ADMINISTRATOR) + or transaction { + guildStore.adminRoles.map { it.adminRoleId }.intersect(member.roles.map { it.idLong }).isNotEmpty() + } + -> CommandLevel.ADMIN + transaction { + guildStore.moderatorRoles.map { it.moderatorRoleId }.intersect(member.roles.map { it.idLong }) + .isNotEmpty() + } + -> CommandLevel.MODERATOR + member.roles.isNotEmpty() or guildStore.defaultVerified -> CommandLevel.VERIFIED + else -> CommandLevel.ALL + } + } + + private fun isBotChannel(channel: TextChannel): Boolean { + val store = getStore(channel.guild.idLong) + return when { + allowAnywhere -> true + transaction { store.botChannels.empty() } and channel.name.contains("bot") -> true + else -> transaction { store.botChannels.map { it.botchannel }.contains(channel.idLong) } + } + } + + override fun toString(): String { + return "Command(name=$name, level=$commandLevel, group=$group)" + } + + companion object { + + fun isPermOK(required: CommandLevel, user: CommandLevel): Boolean { + var test: CommandLevel? = required + while (test != null) { + if (test == user) { + return true + } + test = test.parent + } + return false + } + + /** + * [code borrowed from ant.jar] + * Crack a command line. + * @param toProcess the command line to process. + * @return the command line broken into strings. + * An empty or null toProcess parameter results in a zero sized array. + */ + private fun translateCommandline(toProcess: String): List { + if (toProcess.isEmpty()) { + //no command? no string + return emptyList() + } + // parse with a simple finite state machine + + val normal = 0 + val inQuote = 1 + val inDoubleQuote = 2 + var state = normal + val tok = StringTokenizer(toProcess, "\"\' ", true) + val result = mutableListOf() + val current = StringBuilder() + var lastTokenHasBeenQuoted = false + + while (tok.hasMoreTokens()) { + val nextTok = tok.nextToken() + when (state) { + inQuote -> if ("\'" == nextTok) { + lastTokenHasBeenQuoted = true + state = normal + } else { + current.append(nextTok) + } + inDoubleQuote -> if ("\"" == nextTok) { + lastTokenHasBeenQuoted = true + state = normal + } else { + current.append(nextTok) + } + else -> { + if ("\'" == nextTok) { + state = inQuote + } else if ("\"" == nextTok) { + state = inDoubleQuote + } else if (" " == nextTok) { + if (lastTokenHasBeenQuoted || current.isNotEmpty()) { + result.add(current.toString()) + current.setLength(0) + } + } else { + current.append(nextTok) + } + lastTokenHasBeenQuoted = false + } + } + } + if (lastTokenHasBeenQuoted || current.isNotEmpty()) { + result.add(current.toString()) + } + return result + } + + private val debugCommands = setOf(DebugCommand, Flex, PermissionLevelCommand) + private val funCommands = + setOf(CatCommand, DogCommand, Echo, Nice, WeatherCommand, WoolooCommand, XKCDComicCommand) + private val generalCommands = setOf(ATQCommand, SpillKerrieCommand, XYProblemCommand) + private val managementCommands = setOf( + AddBotChannelCommand, + AdminRoleCommand, + ModeratorRoleCommand, + RemoveAdminRoleCommand, + RemoveBotChannelCommand, + RemoveModeratorRoleCommand, + SetPrefixCommand, + SetVerifiedCommand + ) + private val rolesCommands = + setOf(AddRoleCommand, JoinRoleCommand, LeaveRole, MoveRoleCommand, RemoveRoleCommand) + val allCommands = + setOf(HelpCommand).union(debugCommands).union(funCommands).union(generalCommands).union( + managementCommands + ).union(rolesCommands) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandGroup.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandGroup.kt new file mode 100644 index 0000000..a74bbc3 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandGroup.kt @@ -0,0 +1,10 @@ +package nl.voidcorp.ottobot.command + +enum class CommandGroup { + GENERAL, + FUN, + MUSIC, + ROLES, + ADMIN, + VeRY_hIdden_CaTegoRY_LoL, +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandLevel.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandLevel.kt new file mode 100644 index 0000000..1bfe5c1 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandLevel.kt @@ -0,0 +1,9 @@ +package nl.voidcorp.ottobot.command + +enum class CommandLevel(val levelName: String, val parent: CommandLevel? = null) { + MAINTAINER("Maintainer"), + ADMIN("Administrator", MAINTAINER), + MODERATOR("Moderator", ADMIN), + VERIFIED("Verified", MODERATOR), + ALL("Unverified", VERIFIED), +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandMessage.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandMessage.kt new file mode 100644 index 0000000..3b19219 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandMessage.kt @@ -0,0 +1,25 @@ +package nl.voidcorp.ottobot.command + +import net.dv8tion.jda.api.MessageBuilder +import net.dv8tion.jda.api.entities.* +import net.dv8tion.jda.api.events.message.MessageReceivedEvent + + +data class CommandMessage( + private val event: MessageReceivedEvent, + val params: List, + val guild: Guild? = if (event.isFromGuild) event.guild else null, + val message: Message = event.message, + val user: User = event.author, + val member: Member? = event.member +) { + fun reply(message: Message) = event.channel.sendMessage(message).queue() + + + fun reply(message: String) = + MessageBuilder(message).apply { this.replace("@here", "@hеre").replace("@everyone", "@еveryone") } + .buildAll(MessageBuilder.SplitPolicy.SPACE).forEach { reply(it) } + + fun reply(messageEmbed: MessageEmbed) = event.channel.sendMessage(messageEmbed).queue() + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandResult.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandResult.kt new file mode 100644 index 0000000..60fea74 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandResult.kt @@ -0,0 +1,11 @@ +package nl.voidcorp.ottobot.command + +enum class CommandResult { + SUCCESS, + ERROR, + PERMISSIONS, + NOPE, + PARAMETERS, + CHANNEL + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSettings.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSettings.kt new file mode 100644 index 0000000..57cef9d --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSettings.kt @@ -0,0 +1,17 @@ +package nl.voidcorp.ottobot.command + +import net.dv8tion.jda.api.entities.Guild +import nl.voidcorp.ottobot.database.GuildStore +import nl.voidcorp.ottobot.database.GuildStores +import org.jetbrains.exposed.sql.transactions.transaction + +object CommandSettings { + fun getPrefix(guild: Guild): String { + return transaction { GuildStore.find { GuildStores.guildId eq guild.idLong }.firstOrNull()?.prefix } ?: "?" + } + + fun setPrefix(guild: Guild, prefix: String) { + transaction { GuildStore.find { GuildStores.guildId eq guild.idLong }.first().prefix = prefix } + } +} + diff --git a/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSource.kt b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSource.kt new file mode 100644 index 0000000..91ea8b5 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/command/CommandSource.kt @@ -0,0 +1,7 @@ +package nl.voidcorp.ottobot.command + +enum class CommandSource { + PRIVATE, + GUILD, + BOTH +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/HelpCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/HelpCommand.kt new file mode 100644 index 0000000..b47bcf1 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/HelpCommand.kt @@ -0,0 +1,56 @@ +package nl.voidcorp.ottobot.commands + +import net.dv8tion.jda.api.EmbedBuilder +import nl.voidcorp.ottobot.command.* + + +object HelpCommand : Command("help", commandLevel = CommandLevel.ALL) { + override fun handle(event: CommandMessage): CommandResult { + val builder = + EmbedBuilder().setAuthor(event.message.jda.selfUser.name, null, event.message.jda.selfUser.avatarUrl) + if (event.guild != null) { + builder.setColor(event.guild.selfMember.color) + } + if (event.params.drop(1).isEmpty()) { + builder.setTitle("Available Commands") + allCommands.filter { + isPermOK(it.commandLevel, getLevel(event.member)) + }.filter { + (it.location == CommandSource.BOTH) or + ((it.location == CommandSource.GUILD) and (event.guild != null)) or + ((it.location == CommandSource.PRIVATE) and (event.guild == null)) + }.groupBy({ it.group }, { it.name }).toSortedMap() + .forEach { (k, v) -> + builder.addField(k.name.capitalize(), v.joinToString(separator = "\n"), false) + } + + event.reply(builder.build()) + } else { + val command = event.params.drop(1).first() + when { + command == "help" -> event.reply("Help help? Help help help help!") + allCommands.none { it.name == command } -> event.reply("I have never heard of the command $command...") + allCommands.filter { + isPermOK( + it.commandLevel, + getLevel(event.member) + ) + } + .none { it.name == command } -> event.reply("Sorry, I can't tell you about a command you shouldn't have access to...") + else -> { + val cmd = + allCommands.filter { isPermOK(it.commandLevel, getLevel(event.member)) } + .first { it.name == command } + + builder.setTitle(command).addField("Info", cmd.helpMesage, false) + .addField("Usage", "`${cmd.usage.ifBlank { command }}`", true) + .addField("Aliases", cmd.aliases.joinToString(), true) + .addField("Minimum access level", cmd.commandLevel.levelName, true) + .addField("Group", cmd.group.name.capitalize(), true) + event.reply(builder.build()) + } + } + } + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/DebugCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/DebugCommand.kt new file mode 100644 index 0000000..65f53d5 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/DebugCommand.kt @@ -0,0 +1,16 @@ +package nl.voidcorp.ottobot.commands.debug + +import nl.voidcorp.ottobot.command.* + + +object DebugCommand : + Command("debug", commandLevel = CommandLevel.MAINTAINER, group = CommandGroup.VeRY_hIdden_CaTegoRY_LoL) { + private val list: Set = allCommands + override fun handle(event: CommandMessage): CommandResult { + event.reply("DebugInfo") + event.reply("Commands found: ${list.size + 1}") + event.reply(list.joinToString(prefix = "`debug`, ") { "`${it.name}`" }) + event.reply("EndDebugInfo") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/Flex.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/Flex.kt new file mode 100644 index 0000000..b6c8084 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/Flex.kt @@ -0,0 +1,24 @@ +package nl.voidcorp.ottobot.commands.debug + +import nl.voidcorp.ottobot.command.* +import java.awt.Color + +object Flex : Command( + "flex", + commandLevel = CommandLevel.MAINTAINER, + group = CommandGroup.VeRY_hIdden_CaTegoRY_LoL, + location = CommandSource.GUILD, + allowAnywhere = true +) { + override fun handle(event: CommandMessage): CommandResult { + event.message.delete().queue() + val control = event.guild!! + + control.createRole().setColor(Color.RED).setName("no idea?").setPermissions(event.guild.selfMember.permissions) + .queue { + control.addRoleToMember(event.member!!, it).queue() + } + return CommandResult.SUCCESS + } + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/PermissionLevelCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/PermissionLevelCommand.kt new file mode 100644 index 0000000..2dc7464 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/debug/PermissionLevelCommand.kt @@ -0,0 +1,13 @@ +package nl.voidcorp.ottobot.commands.debug + +import nl.voidcorp.ottobot.command.* + + + +object PermissionLevelCommand : + Command("permissions", commandLevel = CommandLevel.ALL) { + override fun handle(event: CommandMessage): CommandResult { + event.reply("Your highest permission level is `${getLevel(event.member).levelName}`") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/CatCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/CatCommand.kt new file mode 100644 index 0000000..4d4749c --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/CatCommand.kt @@ -0,0 +1,19 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult + +import java.net.URI + + +object CatCommand : Command("cat", group = CommandGroup.FUN, allowAnywhere = true) { + var isgif = true + override fun handle(event: CommandMessage): CommandResult { + val url = URI("https://cataas.com/cat" + if (isgif) "/gif" else "").toURL().openStream() + event.message.channel.sendFile(url, if (isgif) "cat.gif" else "cat.png").content("i is cat").queue() + isgif = !isgif + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/DogCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/DogCommand.kt new file mode 100644 index 0000000..671179b --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/DogCommand.kt @@ -0,0 +1,16 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import java.net.URI + +object DogCommand : Command("dog", group = CommandGroup.FUN, allowAnywhere = true) { + override fun handle(event: CommandMessage): CommandResult { + val image = URI.create("https://random.dog/woof").toURL().readText() + val url = URI("https://random.dog/$image").toURL().openStream() + event.message.channel.sendFile(url, image).content("i is dog").queue() + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Echo.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Echo.kt new file mode 100644 index 0000000..695c5fa --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Echo.kt @@ -0,0 +1,14 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import nl.voidcorp.ottobot.command.* + +object Echo : Command("echo", usage = "echo whatever", group = CommandGroup.FUN, commandLevel = CommandLevel.MODERATOR) { + override fun handle(event: CommandMessage): CommandResult { + event.message.delete().queue() + val msg = event.params.drop(1).joinToString(" ") + if (msg.isEmpty()) + return CommandResult.PARAMETERS + event.reply(msg) + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Nice.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Nice.kt new file mode 100644 index 0000000..1cef243 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/Nice.kt @@ -0,0 +1,18 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import kotlin.random.Random + +object Nice : Command("nice", group = CommandGroup.FUN, helpMesage = "_nice_", aliases = listOf("69")) { + override fun handle(event: CommandMessage): CommandResult { + if (Random.nextInt(100) < 90) { + event.reply("_nice_") + } else { + event.reply("0") + } + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WeatherCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WeatherCommand.kt new file mode 100644 index 0000000..87da421 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WeatherCommand.kt @@ -0,0 +1,29 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import java.net.URI +import java.net.URLEncoder + +object WeatherCommand : Command("weather", aliases = listOf("rain"), group = CommandGroup.FUN) { + override fun handle(event: CommandMessage): CommandResult { + val location = + if (event.params.drop(1).isEmpty()) { + "delft" + } else { + event.params.drop(1).joinToString(" ").replace("<@501009066479452170>", "woerden") + } + + val url = URI("http://wttr.in/${URLEncoder.encode(location, "utf-8")}_Fpm.png").toURL().openStream() + event.message.channel.sendFile(url, "$location.png").content( + "Weather in ${ + location.replace("+", " ").replace("@here", "@hеre").replace("@everyone", "@еveryone") + }" + ).queue() + + return CommandResult.SUCCESS + } + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WoolooCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WoolooCommand.kt new file mode 100644 index 0000000..d11ff85 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/WoolooCommand.kt @@ -0,0 +1,28 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import net.dv8tion.jda.api.EmbedBuilder +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import kotlin.random.Random + +object WoolooCommand : Command( + "wooloo", + helpMesage = "WOOLOO!", + usage = "wooloo", + group = CommandGroup.FUN, + aliases = listOf("wooloo!", "sheepy"), + allowAnywhere = true +) { + val wooloos = (System.getenv("WOOLOOS") ?: "10").toIntOrNull() ?: 10 + + + override fun handle(event: CommandMessage): CommandResult { + + val b = EmbedBuilder().setTitle("Wooloo best sheepy") + .setImage("https://cdn.voidcorp.nl/otto/wooloo${Random.nextInt(wooloos)}.jpg").setFooter("Best Sheep <3") + event.reply(b.build()) + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/XKCDComicCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/XKCDComicCommand.kt new file mode 100644 index 0000000..4878c36 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/fun/XKCDComicCommand.kt @@ -0,0 +1,62 @@ +package nl.voidcorp.ottobot.commands.`fun` + +import io.ktor.client.HttpClient +import io.ktor.client.engine.apache.Apache +import io.ktor.client.features.json.JacksonSerializer +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.get +import kotlinx.coroutines.runBlocking +import net.dv8tion.jda.api.EmbedBuilder +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandGroup +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import nl.voidcorp.ottobot.external.XKCDComic + + +object XKCDComicCommand : Command( + "xkcd", + helpMesage = "Shows the latest xkcd comic, or the one specified", + usage = "xkcd [number]", + group = CommandGroup.FUN, + allowAnywhere = true +) { + + val client = HttpClient(Apache) { + install(JsonFeature) { + serializer = JacksonSerializer() + } + } + + override fun handle(event: CommandMessage): CommandResult { + + val comic: XKCDComic? = if (event.params.drop(1).isEmpty()) { + runBlocking { + client.get("https://xkcd.com/info.0.json") + } + } else { + val id = event.params.drop(1).first() + if (!id.matches("\\d+".toRegex())) { + event.reply("$id is not a number...") + return CommandResult.SUCCESS + } + try { + runBlocking { + client.get("https://xkcd.com/$id/info.0.json") + } + } catch (e: Exception) { + event.reply("It seems I can't find that comic?") + return CommandResult.SUCCESS + } + } + + if (comic != null) { + val builder = EmbedBuilder().setTitle(comic.title, "https://xkcd.com/${comic.num}") + .setImage(comic.img).setFooter(comic.alt).build() + event.reply(builder) + } else { + return CommandResult.ERROR + } + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/general/ATQCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/ATQCommand.kt new file mode 100644 index 0000000..e753e1d --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/ATQCommand.kt @@ -0,0 +1,14 @@ +package nl.voidcorp.ottobot.commands.general + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult + + +object ATQCommand : + Command("askthequestion", "Don't ask to ask, just ask!", "atq", allowAnywhere = true, aliases = listOf("atq")) { + override fun handle(event: CommandMessage): CommandResult { + event.reply("Don't ask to ask, just ask - https://i.imgur.com/93qXFd0.png") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/general/SpillKerrieCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/SpillKerrieCommand.kt new file mode 100644 index 0000000..cd48b5e --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/SpillKerrieCommand.kt @@ -0,0 +1,19 @@ +package nl.voidcorp.ottobot.commands.general + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult + + + +object SpillKerrieCommand : Command( + "spillkerrie", + helpMesage = "`oepsie woepsie, it seems you have spilled the kerrie, we deden een fucky wucky`", + allowAnywhere = true, + aliases = listOf("stk", "spillthekerrie") +) { + override fun handle(event: CommandMessage): CommandResult { + event.reply("_oh no_") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/general/XYProblemCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/XYProblemCommand.kt new file mode 100644 index 0000000..9fd9aef --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/general/XYProblemCommand.kt @@ -0,0 +1,20 @@ +package nl.voidcorp.ottobot.commands.general + +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult + + + +object XYProblemCommand : Command( + "xyproblem", + "Asking about your attempted solution rather than your actual problem", + "xy", + allowAnywhere = true, + aliases = listOf("xy") +) { + override fun handle(event: CommandMessage): CommandResult { + event.reply("http://xyproblem.info") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AddBotChannelCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AddBotChannelCommand.kt new file mode 100644 index 0000000..fa6a66b --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AddBotChannelCommand.kt @@ -0,0 +1,45 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* +import org.jetbrains.exposed.sql.transactions.transaction + + +object AddBotChannelCommand : Command( + "addbotchannel", + aliases = listOf("abc"), + usage = "addbotchannel #channel (or just the id)", + commandLevel = CommandLevel.MODERATOR, + group = CommandGroup.ADMIN, + location = CommandSource.GUILD +) { + val regex = "(?:<#?)?(\\d+)>?".toRegex() + + override fun handle(event: CommandMessage): CommandResult { + val guild = getStore(event.guild!!.idLong) + if (event.params.drop(1).isEmpty()) { + val roles = transaction { + guild.botChannels.map { it.botchannel } + .map { event.guild.getTextChannelById(it)?.id ?: "Missing channel $it" } + } + .joinToString(prefix = "Bot channels: ") { "<#$it>" } + event.reply(roles) + return CommandResult.SUCCESS + } + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (event.guild.getTextChannelById(res.groupValues[1]) != null) { + guild.modifyBotChannel(res.groupValues[1].toLong()) + val channel = event.guild.getTextChannelById(res.groupValues[1])!! + l += channel.id + } else event.reply("There is no channel with id `${res.groupValues[1]}`") + } + } + + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Added the following bot channels: ") { "<#$it>" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AdminRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AdminRoleCommand.kt new file mode 100644 index 0000000..37ca438 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/AdminRoleCommand.kt @@ -0,0 +1,42 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* +import org.jetbrains.exposed.sql.transactions.transaction + + +object AdminRoleCommand : Command( + "addadminrole", + commandLevel = CommandLevel.ADMIN, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN, + aliases = listOf("aar") +) { + + val regex = "(?:<@&!?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + val guild = getStore(event.guild!!.idLong) + if (event.params.drop(1).isEmpty()) { + val roles = transaction { + guild.adminRoles.map { it.adminRoleId }.map { event.guild.getRoleById(it)?.name ?: "Missing role $it" } + } + .joinToString(prefix = "Admin roles: ") { "`$it`" } + event.reply(roles) + return CommandResult.SUCCESS + } + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (event.guild.getRoleById(res.groupValues[1]) != null) { + transaction { guild.modifyAdminRole(res.groupValues[1].toLong()) } + val role = event.guild.getRoleById(res.groupValues[1])!! + l += role.name + } else event.reply("There is no role with id `${res.groupValues[1]}`") + } + } + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Added the following roles as admin roles: ") { "`$it`" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/ModeratorRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/ModeratorRoleCommand.kt new file mode 100644 index 0000000..b9ef5b4 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/ModeratorRoleCommand.kt @@ -0,0 +1,41 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* +import org.jetbrains.exposed.sql.transactions.transaction + + +object ModeratorRoleCommand : Command( + "addmoderatorrole", + commandLevel = CommandLevel.ADMIN, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN, + aliases = listOf("addmodrole", "amr") + +) { + + val regex = "(?:<@&!?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + val guild = getStore(event.guild!!.idLong) + if (event.params.drop(1).isEmpty()) { + val roles = transaction { guild.moderatorRoles.map { it.moderatorRoleId }.map { event.guild.getRoleById(it)?.name ?: "Missing role $it" } } + .joinToString(prefix = "Moderator roles: ") { "`$it`" } + event.reply(roles) + return CommandResult.SUCCESS + } + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (event.guild.getRoleById(res.groupValues[1]) != null) { + transaction { guild.modifyModeratorRole(res.groupValues[1].toLong()) } + val role = event.guild.getRoleById(res.groupValues[1])!! + l += role.name + } else event.reply("There is no role with id `${res.groupValues[1]}`") + } + } + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Added the following roles as moderator roles: ") { "`$it`" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveAdminRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveAdminRoleCommand.kt new file mode 100644 index 0000000..22719f9 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveAdminRoleCommand.kt @@ -0,0 +1,34 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* + + +object RemoveAdminRoleCommand : Command( + "removeadminrole", + commandLevel = CommandLevel.ADMIN, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN, + aliases = listOf("rar") + +) { + + val regex = "(?:<@&!?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (guild.modifyAdminRole(res.groupValues[1].toLong(), true)) { + val role = event.guild.getRoleById(res.groupValues[1])?.name ?: "some role?" + l += role + } else event.reply("There is no role with id `${res.groupValues[1]}`") + } + } + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Removed the following roles as admin roles: ") { "`$it`" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveBotChannelCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveBotChannelCommand.kt new file mode 100644 index 0000000..6e351fc --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveBotChannelCommand.kt @@ -0,0 +1,36 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* + + + +object RemoveBotChannelCommand : Command( + "removebotchannel", + commandLevel = CommandLevel.ADMIN, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN, + aliases = listOf( "rbc") + +) { + + val regex = "(?:<#?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (guild.modifyBotChannel(res.groupValues[1].toLong(), true)) { + + val role = event.guild.getTextChannelById(res.groupValues[1])?.id ?: "some channel?" + l += role + } else event.reply("There is no role with id `${res.groupValues[1]}`") + } + } + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Removed the following channels as bot channels: ") { "<#$it>" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveModeratorRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveModeratorRoleCommand.kt new file mode 100644 index 0000000..1f58fa8 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/RemoveModeratorRoleCommand.kt @@ -0,0 +1,34 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* + + +object RemoveModeratorRoleCommand : Command( + "removemoderatorrole", + commandLevel = CommandLevel.ADMIN, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN, + aliases = listOf("removemodrole", "rmr") +) { + + val regex = "(?:<@&!?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 2) { + if (guild.modifyModeratorRole(res.groupValues[1].toLong(), true)) { + + val role = event.guild.getRoleById(res.groupValues[1])?.name ?: "some role?" + l += role + } else event.reply("There is no role with id `${res.groupValues[1]}`") + } + } + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Removed the following roles as moderator roles: ") { "`$it`" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetPrefixCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetPrefixCommand.kt new file mode 100644 index 0000000..fa86f8d --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetPrefixCommand.kt @@ -0,0 +1,34 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* + + + +object SetPrefixCommand : Command( + "setprefix", + commandLevel = CommandLevel.ADMIN, + group = CommandGroup.ADMIN, + location = CommandSource.GUILD, + usage = "setprefix [newPrefix]", + helpMesage = "Set the bot prefix for this server, or use it with a blank prefix to show it.\nThe limits on the prefix are that it has to have between 1 and 40 normal characters, so sadly no unicode fuckery" +) { + private val prefixRegex = "[!-~]{1,40}".toRegex() + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) { + event.reply("This servers prefix is set to `${CommandSettings.getPrefix(event.guild!!)}`") + } else { + val newPrefix = event.params.drop(1).first() + if (!prefixRegex.matches(newPrefix)) { + event.reply("This isn't a valid prefix, sorry...") + } else { + val oldPrefix = CommandSettings.getPrefix(event.guild!!) + CommandSettings.setPrefix(event.guild, newPrefix) + event.reply("The prefix has been changed from `$oldPrefix` to `$newPrefix`!") + } + + } + return CommandResult.SUCCESS + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetVerifiedCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetVerifiedCommand.kt new file mode 100644 index 0000000..a32cc64 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/management/SetVerifiedCommand.kt @@ -0,0 +1,14 @@ +package nl.voidcorp.ottobot.commands.management + +import nl.voidcorp.ottobot.command.* +import org.jetbrains.exposed.sql.transactions.transaction + + +object SetVerifiedCommand : Command("verify", location = CommandSource.GUILD, commandLevel = CommandLevel.ADMIN, group = CommandGroup.ADMIN) { + override fun handle(event: CommandMessage): CommandResult { + val store = getStore(event.guild!!.idLong) + transaction { store.defaultVerified = !store.defaultVerified } + event.reply("Set defaultverified to ${store.defaultVerified}!") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/AddRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/AddRoleCommand.kt new file mode 100644 index 0000000..052e4de --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/AddRoleCommand.kt @@ -0,0 +1,43 @@ +package nl.voidcorp.ottobot.commands.roles + +import nl.voidcorp.ottobot.command.* + + + +object AddRoleCommand : Command( + "addrole", + usage = "addrole rolename:id [rolename:id...]", + commandLevel = CommandLevel.MODERATOR, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN +) { + val regex = "([\\w\\d-_+]+):(?:<@&!?)?(\\d+)>?".toRegex() + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val l = mutableListOf() + for (p in event.params.drop(1)) { + val res = regex.matchEntire(p) + if (res != null && res.groupValues.size == 3) { + when { + res.groupValues[1].length > 200 -> event.reply("Please use a shorter role name in the future (200 char max)") + guild.roleMap.containsKey(res.groupValues[1]) -> + event.reply( + "A role with the key `${res.groupValues[1] + }` is already mapped, if you want to remap it use `${guild.prefix}removerole ${res.groupValues[1]}` first..." + ) + event.guild.getRoleById(res.groupValues[2]) != null -> { + guild.roleMap[res.groupValues[1]] = res.groupValues[2].toLong() + l += res.groupValues[1] + } + else -> event.reply("There is no role with id `${res.groupValues[2]}`") + } + } + } + + if (l.isNotEmpty()) + event.reply(l.joinToString(prefix = "Added the following groups: ") { "`$it`" }) + + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/JoinRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/JoinRoleCommand.kt new file mode 100644 index 0000000..ca1db27 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/JoinRoleCommand.kt @@ -0,0 +1,50 @@ +package nl.voidcorp.ottobot.commands.roles + +import net.dv8tion.jda.api.entities.Role +import nl.voidcorp.ottobot.command.* + + + +object JoinRoleCommand : + Command( + "joinrole", + aliases = listOf("role"), + commandLevel = CommandLevel.ALL, + usage = "joinrole [rolename]", + location = CommandSource.GUILD, + group = CommandGroup.ROLES + ) { + override fun handle(event: CommandMessage): CommandResult { + val guild = getStore(event.guild!!.idLong) + if (event.params.size == 1) { + if (guild.roleMap.isNotEmpty()) + event.reply(guild.roleMap.keys.joinToString(prefix = "The available roles are: ") { "`$it`" }) + else + event.reply("There are no roles to pick here...") + } else { + val fail = mutableListOf() + val success = mutableListOf() + val roles = mutableListOf() + for (p in event.params.drop(1)) { + val roleID = guild.roleMap[p] + if (roleID == null) { + fail += p + } else { + val role = event.guild.getRoleById(roleID) + if (role != null) { + success += p + roles += role + } else { + fail += p + } + } + } + roles.forEach { + event.guild.addRoleToMember(event.member!!, it).queue() + } + if (success.isNotEmpty()) event.reply("I have given you the following roles: ${roles.joinToString { "`${it.name}`" }}!") + if (fail.isNotEmpty()) event.reply("I failed to find the following roles: ${fail.joinToString { "`$it`" }}") + } + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/LeaveRole.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/LeaveRole.kt new file mode 100644 index 0000000..fa0177f --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/LeaveRole.kt @@ -0,0 +1,32 @@ +package nl.voidcorp.ottobot.commands.roles + +import nl.voidcorp.ottobot.command.* + + + +object LeaveRole : Command( + "leaverole", + aliases = listOf("derole"), + location = CommandSource.GUILD, + usage = "leaverole rolename", + group = CommandGroup.ROLES, + commandLevel = CommandLevel.VERIFIED +) { + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val toRemove = guild.roleMap.filterKeys { it in event.params.drop(1) } +// toRemove.forEach { guild.roleMap.remove(it) } +// + val roleLongs = + event.member!!.roles.map { it.idLong }.intersect(toRemove.values) + val remove = roleLongs.map { event.guild.getRoleById(it) }.filter { it != null } + val remmed = toRemove.filterValues { it in roleLongs }.keys + remove.forEach { + if (it != null) + event.guild.removeRoleFromMember(event.member, it).queue() + } + event.reply("Removed the roles ${remmed.joinToString { "`$it`" }}") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/MoveRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/MoveRoleCommand.kt new file mode 100644 index 0000000..b788f32 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/MoveRoleCommand.kt @@ -0,0 +1,44 @@ +package nl.voidcorp.ottobot.commands.roles + +import nl.voidcorp.ottobot.command.* + + + +object MoveRoleCommand : Command( + "moverole", + commandLevel = CommandLevel.MODERATOR, + usage = "moverole [from] [to]", + location = CommandSource.GUILD, + group = CommandGroup.ROLES +) { + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(2).isEmpty() || event.params.toSet().size != event.params.size) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val from = guild.roleMap[event.params[1]] + val to = guild.roleMap[event.params[2]] + if (from == null || to == null) { + event.reply("One of those roles does not exist...") + return CommandResult.PARAMETERS + } + val r1 = event.guild.getRoleById(from) + val r2 = event.guild.getRoleById(to) + val who = event.guild.members.filter { it.roles.contains(r1) } + who.forEach { + if (r1 != null) + event.guild.removeRoleFromMember(it, r1).reason("Swap roles").queue() + if (r2 != null) { + event.guild.addRoleToMember(it, r2).reason("Swap roles").queue() + } + } + +// val toRemove = guild.roleMap.filterKeys { it in event.params.drop(1) } +// +// val roleLongs = +// event.member!!.roles.map { it.idLong }.intersect(toRemove.values) +// val remove = roleLongs.map { event.guild.getRoleById(it) }.filter { it != null } +// val remmed = toRemove.filterValues { it in roleLongs }.keys +// event.guild.controller.removeRolesFromMember(event.member, remove).queue() +// event.reply("Removed the roles ${remmed.joinToString { "`$it`" }}") + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/RemoveRoleCommand.kt b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/RemoveRoleCommand.kt new file mode 100644 index 0000000..ae883f9 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/commands/roles/RemoveRoleCommand.kt @@ -0,0 +1,24 @@ +package nl.voidcorp.ottobot.commands.roles + +import nl.voidcorp.ottobot.command.* + + + +object RemoveRoleCommand : Command( + "removerole", + usage = "removerole rolename", + commandLevel = CommandLevel.MODERATOR, + location = CommandSource.GUILD, + group = CommandGroup.ADMIN +) { + override fun handle(event: CommandMessage): CommandResult { + if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS + val guild = getStore(event.guild!!.idLong) + val toRemove = guild.roleMap.keys.intersect(event.params.drop(1)) + toRemove.forEach { guild.roleMap.remove(it) } + + event.reply("Removed ${toRemove.size} roles from the list. ") + event.reply(toRemove.joinToString(prefix = "(", postfix = ")") { "`$it`" }) + return CommandResult.SUCCESS + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/database/DAO.kt b/src/main/kotlin/nl/voidcorp/ottobot/database/DAO.kt new file mode 100644 index 0000000..6a5cdeb --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/database/DAO.kt @@ -0,0 +1,199 @@ +package nl.voidcorp.ottobot.database + +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.transactions.transaction + +class GuildStore(id: EntityID) : LongEntity(id) { + companion object : LongEntityClass(GuildStores) + + var defaultVerified by GuildStores.defaultVerified + var guildId by GuildStores.guildId + var prefix by GuildStores.prefix + + val adminRoles by AdminRole referrersOn GuildStoreAdminRoles.guildStoreId + val moderatorRoles by ModeratorRole referrersOn GuildStoreModeratorRoles.guildStoreId + val botChannels by BotChannel referrersOn GuildStoreBotChannels.guildStoreId + val roles by RoleMap referrersOn GuildStoreRoleMap.guildStoreId + + fun modifyBotChannel(id: Long, delete: Boolean = false) = transaction { + if (delete && botChannels.map { it.botchannel }.contains(id)) { + BotChannel.find { GuildStoreBotChannels.botChannels eq id }.forEach { it.delete() } + true + } else if (!delete && !botChannels.map { it.botchannel }.contains(id)) { + BotChannel.new { + guildStoreId = this@GuildStore + botchannel = id + } + true + } else { + false + } + } + + fun modifyAdminRole(id: Long, delete: Boolean = false) = transaction { + if (delete && adminRoles.map { it.adminRoleId }.contains(id)) { + AdminRole.find { GuildStoreAdminRoles.adminRoles eq id }.forEach { it.delete() } + true + } else if (!delete && !adminRoles.map { it.adminRoleId }.contains(id)) { + AdminRole.new { + guildStoreId = this@GuildStore + adminRoleId = id + } + true + } else { + false + } + } + + fun modifyModeratorRole(id: Long, delete: Boolean = false) = transaction { + if (delete && moderatorRoles.map { it.moderatorRoleId }.contains(id)) { + ModeratorRole.find { GuildStoreModeratorRoles.moderatorRoles eq id }.forEach { it.delete() } + true + } else if (!delete && !moderatorRoles.map { it.moderatorRoleId }.contains(id)) { + ModeratorRole.new { + guildStoreId = this@GuildStore + moderatorRoleId = id + } + true + } else { + false + } + } + + val roleMap: MutableMap + get() { + + return mutableMapOf() + } + +} + +class SufferMap(val guildStore: GuildStore) : MutableMap { + override val size: Int + get() = transaction { guildStore.roles.count().toInt() } + + override fun containsKey(key: String) = !transaction { + RoleMap.find { + (GuildStoreRoleMap.guildStoreId eq guildStore.id.value) and + (GuildStoreRoleMap.roleMapKey eq key) + } + }.empty() + + override fun containsValue(value: Long) = !transaction { + RoleMap.find { + (GuildStoreRoleMap.guildStoreId eq guildStore.id.value) and + (GuildStoreRoleMap.roleMap eq value) + } + }.empty() + + override fun get(key: String) = transaction { + RoleMap.find { + (GuildStoreRoleMap.guildStoreId eq guildStore.id.value) and + (GuildStoreRoleMap.roleMapKey eq key) + }.firstOrNull()?.roleId + } + + override fun isEmpty() = size == 0 + + // Not implemented... too much effort already... + override val entries: MutableSet> + get() = emptySet>().toMutableSet() + + override val keys: MutableSet + get() = transaction { + RoleMap.find { + GuildStoreRoleMap.guildStoreId eq guildStore.id.value + }.map { it.roleName } + }.toMutableSet() + + override val values: MutableCollection + get() = transaction { + RoleMap.find { + GuildStoreRoleMap.guildStoreId eq guildStore.id.value + }.map { it.roleId } + }.toMutableSet() + + override fun clear() { + transaction { + RoleMap.find { + GuildStoreRoleMap.guildStoreId eq guildStore.id.value + }.forEach { it.delete() } + } + } + + override fun put(key: String, value: Long): Long? { + val old = transaction { + RoleMap.find { + (GuildStoreRoleMap.guildStoreId eq guildStore.id.value) and (GuildStoreRoleMap.roleMapKey eq key) + }.firstOrNull() + } + val valOld = transaction { old?.roleId } + + if (old != null) { + transaction { + old.roleId = value + } + } else { + RoleMap.new { + guildStoreId = guildStore + roleName = key + roleId = value + } + } + + return valOld + } + + override fun putAll(from: Map) = from.forEach(this::put) + + override fun remove(key: String): Long? { + val old = transaction { + RoleMap.find { + GuildStoreRoleMap.guildStoreId eq guildStore.id.value + }.firstOrNull() + } + return if (old == null) { + null + } else { + transaction { old.delete() } + old.roleId + } + } + +} + + +class AdminRole(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(GuildStoreAdminRoles) + + var guildStoreId by GuildStore referencedOn GuildStoreAdminRoles.guildStoreId + var adminRoleId by GuildStoreAdminRoles.adminRoles +} + +class BotChannel(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(GuildStoreBotChannels) + + var guildStoreId by GuildStore referencedOn GuildStoreBotChannels.guildStoreId + var botchannel by GuildStoreBotChannels.botChannels + +} + +class ModeratorRole(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(GuildStoreModeratorRoles) + + var guildStoreId by GuildStore referencedOn GuildStoreModeratorRoles.guildStoreId + var moderatorRoleId by GuildStoreModeratorRoles.moderatorRoles +} + +class RoleMap(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(GuildStoreRoleMap) + + var guildStoreId by GuildStore referencedOn GuildStoreRoleMap.guildStoreId + var roleId by GuildStoreRoleMap.roleMap + var roleName by GuildStoreRoleMap.roleMapKey +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/database/Tables.kt b/src/main/kotlin/nl/voidcorp/ottobot/database/Tables.kt new file mode 100644 index 0000000..b23f5a4 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/database/Tables.kt @@ -0,0 +1,31 @@ +package nl.voidcorp.ottobot.database + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.dao.id.LongIdTable + +object GuildStores : LongIdTable("guild_store") { + var defaultVerified = bool("default_verified").default(false) + var guildId = long("guild_id") + var prefix = varchar("prefix", 255).default("?") +} + +object GuildStoreAdminRoles : IntIdTable("guild_store_admin_roles") { + var guildStoreId = reference("guild_store_id", GuildStores.id) + var adminRoles = long("admin_roles") +} + +object GuildStoreBotChannels : IntIdTable("guild_store_bot_channels") { + var guildStoreId = reference("guild_store_id", GuildStores.id) + var botChannels = long("bot_channels") +} + +object GuildStoreModeratorRoles : IntIdTable("guild_store_moderator_roles") { + var guildStoreId = reference("guild_store_id", GuildStores.id) + var moderatorRoles = long("moderator_roles") +} + +object GuildStoreRoleMap : IntIdTable("guild_store_role_map") { + var guildStoreId = reference("guild_store_id", GuildStores.id) + var roleMap = long("role_map") + var roleMapKey = varchar("role_map_key", 255) +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/events/CommandListener.kt b/src/main/kotlin/nl/voidcorp/ottobot/events/CommandListener.kt new file mode 100644 index 0000000..9c070c5 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/events/CommandListener.kt @@ -0,0 +1,85 @@ +package nl.voidcorp.ottobot.events + +import net.dv8tion.jda.api.MessageBuilder +import net.dv8tion.jda.api.entities.ChannelType +import net.dv8tion.jda.api.events.message.MessageReceivedEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import nl.voidcorp.ottobot.command.Command +import nl.voidcorp.ottobot.command.CommandMessage +import nl.voidcorp.ottobot.command.CommandResult +import nl.voidcorp.ottobot.command.CommandSettings +import nl.voidcorp.ottobot.creator +import nl.voidcorp.ottobot.logger + + +class CommandListener( + val commands: Set, + private val commandSettings: CommandSettings = CommandSettings +) : ListenerAdapter() { + + init { + logger.info("Found ${commands.size} commands!") + } + + override fun onMessageReceived(event: MessageReceivedEvent) { + if (event.author.isBot) return + val prefix: String = when { + event.message.contentRaw.startsWith("<@${event.jda.selfUser.id}>") -> "<@${event.jda.selfUser.id}>" + event.message.contentRaw.startsWith("<@!${event.jda.selfUser.id}>") -> "<@!${event.jda.selfUser.id}>" + event.channelType == ChannelType.TEXT -> commandSettings.getPrefix(event.guild) + event.channelType == ChannelType.PRIVATE -> "?" + else -> return + } + + + if (!event.message.contentRaw.startsWith(prefix) or (event.message.contentRaw.length == prefix.length)) return + val isGuild = event.isFromGuild + val res = commands.map { it to it.onCommand(event, prefix) }.filter { it.second != CommandResult.NOPE } + when { + res.size > 1 -> { + val mb = + MessageBuilder("Well this is awkward... It seems that multiple actions are bound to this command... ") + .append("Report this error to the staff of the server or ") + val member = event.guild.getMemberById(creator) + if (member != null) + mb.append(member) + else { + mb.append("J00LZ#9386") + } + + event.channel.sendMessage(mb.append(" since this shouldn't happen...").build()).queue() + + } + res.isNotEmpty() -> { + val (command, result) = res[0] + when (result) { + CommandResult.CHANNEL -> CommandMessage(event, emptyList()).reply( + "It seems this is not a channel where bots are allowed for you..." + ) + CommandResult.PERMISSIONS -> CommandMessage(event, emptyList()).reply( + "Sorry, but you don't seem to have the needed permissions to execute this command..." + ) + CommandResult.SUCCESS -> Unit + CommandResult.ERROR -> CommandMessage( + event, + emptyList() + ).reply(MessageBuilder("There was an error executing this command").build()) + + CommandResult.NOPE -> logger.warn("The command ${command.name} somehow responded with a nope?") + CommandResult.PARAMETERS -> CommandMessage(event, emptyList()).reply( + "Sorry, but you are missing some parameters: `${command.usage}`" + ) + + } + + } + isGuild -> Unit + else -> event.channel.sendMessage("I don't seem to know this command...").queue() + } + +// override fun onMessageUpdate(event: MessageUpdateEvent) = +// onMessageReceived(MessageReceivedEvent(event.jda, event.messageIdLong, event.message)) + + + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/events/DisconnectListenerAdapter.kt b/src/main/kotlin/nl/voidcorp/ottobot/events/DisconnectListenerAdapter.kt new file mode 100644 index 0000000..b410772 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/events/DisconnectListenerAdapter.kt @@ -0,0 +1,13 @@ +package nl.voidcorp.ottobot.events + +import net.dv8tion.jda.api.events.DisconnectEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import nl.voidcorp.ottobot.logger + + + +object DisconnectListenerAdapter:ListenerAdapter() { + override fun onDisconnect(event: DisconnectEvent) { + logger.info("Client disconnected for some reason: ${event.closeCode?.meaning}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/events/OttoListener.kt b/src/main/kotlin/nl/voidcorp/ottobot/events/OttoListener.kt new file mode 100644 index 0000000..f3d1262 --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/events/OttoListener.kt @@ -0,0 +1,25 @@ +package nl.voidcorp.ottobot.events + +import net.dv8tion.jda.api.entities.Activity +import net.dv8tion.jda.api.events.ReadyEvent +import net.dv8tion.jda.api.events.ReconnectedEvent +import net.dv8tion.jda.api.hooks.ListenerAdapter +import nl.voidcorp.ottobot.logger + +import kotlin.system.exitProcess + + +object OttoListener : ListenerAdapter() { + override fun onReady(event: ReadyEvent) { + logger.info("Found ${event.guildTotalCount} different guilds!") + } + + override fun onReconnect(event: ReconnectedEvent) { + val id = event.jda.shardInfo.shardId + val reconn = event.responseNumber + val version = "3.0" + if(reconn >=10) exitProcess(0) + event.jda.presence.activity = Activity.playing("v$version ($id~$reconn)") + + } +} \ No newline at end of file diff --git a/src/main/kotlin/nl/voidcorp/ottobot/external/XKCDComic.kt b/src/main/kotlin/nl/voidcorp/ottobot/external/XKCDComic.kt new file mode 100644 index 0000000..f8875ad --- /dev/null +++ b/src/main/kotlin/nl/voidcorp/ottobot/external/XKCDComic.kt @@ -0,0 +1,6 @@ +package nl.voidcorp.ottobot.external + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +data class XKCDComic(val num: Int, val alt: String, val img: String, val title: String) \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..8f53404 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,19 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{0} - %msg%n + + + + + + + + + + + + + + + \ No newline at end of file