| @ -0,0 +1,145 @@ | |||
| # Created by https://www.toptal.com/developers/gitignore/api/intellij+all,kotlin,gradle | |||
| # Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,kotlin,gradle | |||
| ### Intellij+all ### | |||
| # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider | |||
| # 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 | |||
| # AWS User-specific | |||
| .idea/**/aws.xml | |||
| # 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/artifacts | |||
| # .idea/compiler.xml | |||
| # .idea/jarRepositories.xml | |||
| # .idea/modules.xml | |||
| # .idea/*.iml | |||
| # .idea/modules | |||
| # *.iml | |||
| # *.ipr | |||
| # 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 | |||
| # SonarLint plugin | |||
| .idea/sonarlint/ | |||
| # 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+all Patch ### | |||
| # Ignore everything but code style settings and run configurations | |||
| # that are supposed to be shared within teams. | |||
| .idea/* | |||
| !.idea/codeStyles | |||
| !.idea/runConfigurations | |||
| ### Kotlin ### | |||
| # 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* | |||
| replay_pid* | |||
| ### Gradle ### | |||
| .gradle | |||
| **/build/ | |||
| !src/**/build/ | |||
| # Ignore Gradle GUI config | |||
| gradle-app.setting | |||
| # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) | |||
| !gradle-wrapper.jar | |||
| # Avoid ignore Gradle wrappper properties | |||
| !gradle-wrapper.properties | |||
| # Cache of project | |||
| .gradletasknamecache | |||
| # Eclipse Gradle plugin generated files | |||
| # Eclipse Core | |||
| .project | |||
| # JDT-specific (Eclipse Java Development Tools) | |||
| .classpath | |||
| ### Gradle Patch ### | |||
| # Java heap dump | |||
| *.hprof | |||
| # End of https://www.toptal.com/developers/gitignore/api/intellij+all,kotlin,gradle | |||
| @ -0,0 +1,76 @@ | |||
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | |||
| plugins { | |||
| idea | |||
| application | |||
| kotlin("jvm") version "1.7.20" | |||
| id("com.github.johnrengelman.shadow") version "7.1.2" | |||
| id("com.github.gmazzo.buildconfig") version "3.1.0" | |||
| } | |||
| val commit = runCommand(arrayListOf("git", "rev-parse", "HEAD")) | |||
| val changes = runCommand(arrayListOf("git", "diff", "--shortstat")) | |||
| group = "xyz.brettb.discord.ieeevents" | |||
| version = "${rootProject.findProperty("major")}.${rootProject.findProperty("minor")}.${rootProject.findProperty("patch")}" | |||
| application { | |||
| mainClass.set("xyz.brettb.discord.ieeevents.IEEEventsKt") | |||
| } | |||
| buildConfig { | |||
| className("IEEEventsBotInfo") | |||
| buildConfigField("String", "VERSION", "\"${version}\"") | |||
| buildConfigField("String", "COMMIT", "\"$commit\"") | |||
| buildConfigField("String", "LOCAL_CHANGES", "\"$changes\"") | |||
| buildConfigField("long", "BUILD_TIME", "${System.currentTimeMillis()}L") | |||
| } | |||
| repositories { | |||
| mavenCentral() | |||
| maven { | |||
| name = "brettb-repo" | |||
| url = uri("https://repo.brettb.xyz") | |||
| } | |||
| maven("https://m2.dv8tion.net/releases") { | |||
| name = "m2-dv8tion" | |||
| } | |||
| } | |||
| dependencies { | |||
| listOf("stdlib-jdk8", "reflect").forEach { implementation(kotlin(it)) } | |||
| implementation("net.dv8tion:JDA:4.4.0_350") | |||
| implementation("tech.junodevs.discord:kriess:0.14.0") | |||
| implementation("ch.qos.logback:logback-classic:1.4.4") | |||
| implementation("org.mnode.ical4j:ical4j:3.2.6") | |||
| implementation("com.squareup.okhttp3:okhttp:4.10.0") | |||
| // Utilities | |||
| implementation("org.yaml:snakeyaml:1.31") | |||
| implementation(kotlin("stdlib-jdk8")) | |||
| } | |||
| tasks.test { | |||
| useJUnitPlatform() | |||
| } | |||
| tasks.withType<KotlinCompile> { | |||
| kotlinOptions.jvmTarget = "1.8" | |||
| } | |||
| fun runCommand(commands: List<String>): String { | |||
| val stdout = org.apache.commons.io.output.ByteArrayOutputStream() | |||
| exec { | |||
| commandLine = commands | |||
| standardOutput = stdout | |||
| } | |||
| return stdout.toString("utf-8").trim() | |||
| } | |||
| @ -0,0 +1,5 @@ | |||
| major=0 | |||
| minor=1 | |||
| patch=0 | |||
| kotlin.code.style=official | |||
| @ -0,0 +1,5 @@ | |||
| distributionBase=GRADLE_USER_HOME | |||
| distributionPath=wrapper/dists | |||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip | |||
| zipStoreBase=GRADLE_USER_HOME | |||
| zipStorePath=wrapper/dists | |||
| @ -0,0 +1,234 @@ | |||
| #!/bin/sh | |||
| # | |||
| # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. | |||
| # | |||
| # Important for running: | |||
| # | |||
| # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | |||
| # noncompliant, but you have some other compliant shell such as ksh or | |||
| # bash, then to run this script, type that shell name before the whole | |||
| # command line, like: | |||
| # | |||
| # ksh Gradle | |||
| # | |||
| # Busybox and similar reduced shells will NOT work, because this script | |||
| # requires all of these POSIX shell features: | |||
| # * functions; | |||
| # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | |||
| # «${var#prefix}», «${var%suffix}», and «$( cmd )»; | |||
| # * compound commands having a testable exit status, especially «case»; | |||
| # * various built-in commands including «command», «set», and «ulimit». | |||
| # | |||
| # Important for patching: | |||
| # | |||
| # (2) This script targets any POSIX shell, so it avoids extensions provided | |||
| # by Bash, Ksh, etc; in particular arrays are avoided. | |||
| # | |||
| # The "traditional" practice of packing multiple parameters into a | |||
| # space-separated string is a well documented source of bugs and security | |||
| # problems, so this is (mostly) avoided, by progressively accumulating | |||
| # options in "$@", and eventually passing that to Java. | |||
| # | |||
| # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | |||
| # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | |||
| # see the in-line comments for details. | |||
| # | |||
| # There are tweaks for specific operating systems such as AIX, CygWin, | |||
| # Darwin, MinGW, and NonStop. | |||
| # | |||
| # (3) This script is generated from the Groovy template | |||
| # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | |||
| # within the Gradle project. | |||
| # | |||
| # You can find Gradle at https://github.com/gradle/gradle/. | |||
| # | |||
| ############################################################################## | |||
| # Attempt to set APP_HOME | |||
| # Resolve links: $0 may be a link | |||
| app_path=$0 | |||
| # Need this for daisy-chained symlinks. | |||
| while | |||
| APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path | |||
| [ -h "$app_path" ] | |||
| do | |||
| ls=$( ls -ld "$app_path" ) | |||
| link=${ls#*' -> '} | |||
| case $link in #( | |||
| /*) app_path=$link ;; #( | |||
| *) app_path=$APP_HOME$link ;; | |||
| esac | |||
| done | |||
| APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit | |||
| APP_NAME="Gradle" | |||
| APP_BASE_NAME=${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 "$*" | |||
| } >&2 | |||
| die () { | |||
| echo | |||
| echo "$*" | |||
| echo | |||
| exit 1 | |||
| } >&2 | |||
| # 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 ;; #( | |||
| MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then | |||
| case $MAX_FD in #( | |||
| max*) | |||
| MAX_FD=$( ulimit -H -n ) || | |||
| warn "Could not query maximum file descriptor limit" | |||
| esac | |||
| case $MAX_FD in #( | |||
| '' | soft) :;; #( | |||
| *) | |||
| ulimit -n "$MAX_FD" || | |||
| warn "Could not set maximum file descriptor limit to $MAX_FD" | |||
| esac | |||
| fi | |||
| # Collect all arguments for the java command, stacking in reverse order: | |||
| # * args from the command line | |||
| # * the main class name | |||
| # * -classpath | |||
| # * -D...appname settings | |||
| # * --module-path (only if needed) | |||
| # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | |||
| # For Cygwin or MSYS, switch paths to Windows format before running java | |||
| if "$cygwin" || "$msys" ; then | |||
| APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | |||
| CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | |||
| JAVACMD=$( cygpath --unix "$JAVACMD" ) | |||
| # Now convert the arguments - kludge to limit ourselves to /bin/sh | |||
| for arg do | |||
| if | |||
| case $arg in #( | |||
| -*) false ;; # don't mess with options #( | |||
| /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath | |||
| [ -e "$t" ] ;; #( | |||
| *) false ;; | |||
| esac | |||
| then | |||
| arg=$( cygpath --path --ignore --mixed "$arg" ) | |||
| fi | |||
| # Roll the args list around exactly as many times as the number of | |||
| # args, so each arg winds up back in the position where it started, but | |||
| # possibly modified. | |||
| # | |||
| # NB: a `for` loop captures its iteration list before it begins, so | |||
| # changing the positional parameters here affects neither the number of | |||
| # iterations, nor the values presented in `arg`. | |||
| shift # remove old arg | |||
| set -- "$@" "$arg" # push replacement arg | |||
| done | |||
| fi | |||
| # Collect all arguments for the java command; | |||
| # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of | |||
| # shell script including quotes and variable substitutions, so put them in | |||
| # double quotes to make sure that they get re-expanded; and | |||
| # * put everything else in single quotes, so that it's not re-expanded. | |||
| set -- \ | |||
| "-Dorg.gradle.appname=$APP_BASE_NAME" \ | |||
| -classpath "$CLASSPATH" \ | |||
| org.gradle.wrapper.GradleWrapperMain \ | |||
| "$@" | |||
| # Use "xargs" to parse quoted args. | |||
| # | |||
| # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | |||
| # | |||
| # In Bash we could simply go: | |||
| # | |||
| # readarray ARGS < <( xargs -n1 <<<"$var" ) && | |||
| # set -- "${ARGS[@]}" "$@" | |||
| # | |||
| # but POSIX shell has neither arrays nor command substitution, so instead we | |||
| # post-process each arg (as a line of input to sed) to backslash-escape any | |||
| # character that might be a shell metacharacter, then use eval to reverse | |||
| # that process (while maintaining the separation between arguments), and wrap | |||
| # the whole thing up as a single "set" statement. | |||
| # | |||
| # This will of course break if any of these variables contains a newline or | |||
| # an unmatched quote. | |||
| # | |||
| eval "set -- $( | |||
| printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | |||
| xargs -n1 | | |||
| sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | |||
| tr '\n' ' ' | |||
| )" '"$@"' | |||
| exec "$JAVACMD" "$@" | |||
| @ -0,0 +1,89 @@ | |||
| @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 execute | |||
| 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 execute | |||
| 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 | |||
| :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 %* | |||
| :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 | |||
| @ -0,0 +1,4 @@ | |||
| 515646289250222098: | |||
| prefix: '-' | |||
| eventChannelID: null | |||
| mirrorEvents: false | |||
| @ -0,0 +1,3 @@ | |||
| rootProject.name = "IEEEvents" | |||
| @ -0,0 +1,9 @@ | |||
| package xyz.brettb.discord.ieeevents | |||
| object Constants { | |||
| object Colors { | |||
| val BLUE = 0x0066A1 | |||
| } | |||
| } | |||
| @ -0,0 +1,194 @@ | |||
| package xyz.brettb.discord.ieeevents | |||
| import ch.qos.logback.classic.Level | |||
| import ch.qos.logback.classic.Logger | |||
| import net.dv8tion.jda.api.JDA | |||
| import net.dv8tion.jda.api.JDABuilder | |||
| import net.dv8tion.jda.api.Permission | |||
| import net.dv8tion.jda.api.events.GenericEvent | |||
| import net.dv8tion.jda.api.events.ReadyEvent | |||
| import net.dv8tion.jda.api.hooks.EventListener | |||
| import net.dv8tion.jda.api.requests.GatewayIntent | |||
| import net.dv8tion.jda.api.utils.ChunkingFilter | |||
| import net.dv8tion.jda.api.utils.MemberCachePolicy | |||
| import net.fortuna.ical4j.data.CalendarBuilder | |||
| import net.fortuna.ical4j.filter.predicate.PeriodRule | |||
| import net.fortuna.ical4j.model.Calendar | |||
| import net.fortuna.ical4j.model.Date | |||
| import net.fortuna.ical4j.model.DateTime | |||
| import net.fortuna.ical4j.model.Period | |||
| import net.fortuna.ical4j.model.component.VEvent | |||
| import org.slf4j.LoggerFactory | |||
| import org.yaml.snakeyaml.Yaml | |||
| import tech.junodevs.discord.kriess.impl.managers.CommandManager | |||
| import tech.junodevs.discord.kriess.menus.MenuListener | |||
| import xyz.brettb.discord.ieeevents.commands.CommandBase | |||
| import xyz.brettb.discord.ieeevents.commands.info.HelpCommand | |||
| import xyz.brettb.discord.ieeevents.commands.info.UpcomingEventsCommand | |||
| import xyz.brettb.discord.ieeevents.commands.settings.ChangePrefixCommand | |||
| import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettings | |||
| import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettingsManager | |||
| import xyz.brettb.discord.ieeevents.services.StatusService | |||
| import java.io.File | |||
| import java.io.FileInputStream | |||
| import java.nio.file.Files | |||
| import java.time.Duration | |||
| import java.time.temporal.ChronoUnit | |||
| import kotlin.system.exitProcess | |||
| val logger = LoggerFactory.getLogger(IEEEventsBot.javaClass) | |||
| fun main() { | |||
| logger.info("IEEEvents Bot") | |||
| IEEEventsBot.load() | |||
| IEEEventsBot.JDA = JDABuilder | |||
| .createDefault(IEEEventsBot.token) | |||
| .enableIntents(GatewayIntent.GUILD_MESSAGES, GatewayIntent.GUILD_MEMBERS) | |||
| .addEventListeners(IEEEventsBot.commandManager, IEEEventsBot, MenuListener) | |||
| .setChunkingFilter(ChunkingFilter.ALL) | |||
| .setMemberCachePolicy(MemberCachePolicy.ALL) | |||
| .build() | |||
| // Give the person running the bot an invite URL | |||
| logger.info( | |||
| "Invite @ ${ | |||
| IEEEventsBot.JDA.getInviteUrl( | |||
| Permission.MESSAGE_WRITE, | |||
| Permission.MESSAGE_MANAGE, | |||
| Permission.MESSAGE_EMBED_LINKS, | |||
| Permission.MESSAGE_ATTACH_FILES, | |||
| Permission.MESSAGE_ADD_REACTION | |||
| ) | |||
| }" | |||
| ) | |||
| // Ensure the StatusService stops correctly | |||
| Runtime.getRuntime().addShutdownHook(Thread { | |||
| StatusService.shutdown() | |||
| }) | |||
| } | |||
| object IEEEventsBot : EventListener { | |||
| /** | |||
| * The [JDA] instance used by the bot. | |||
| */ | |||
| lateinit var JDA: JDA | |||
| /** | |||
| * The [CommandManager] used by the bot. | |||
| */ | |||
| lateinit var commandManager: CommandManager<IEEEventsGuildSettings> | |||
| /** | |||
| * The YAML instance used. | |||
| */ | |||
| private val yaml = Yaml() | |||
| /** | |||
| * The bot's default prefix. | |||
| */ | |||
| lateinit var prefix: String | |||
| /** | |||
| * The bot's login token. | |||
| */ | |||
| lateinit var token: String | |||
| /** | |||
| * The calendar url that we're attempting to mirror between discord + the source. | |||
| */ | |||
| lateinit var calendarUrl: String | |||
| /** | |||
| * The iCal [Calendar] object gotten from the [calendarUrl]. | |||
| */ | |||
| val calendar: Calendar? | |||
| get() = | |||
| try { | |||
| val inputStream = Utils.downloadCalendar(calendarUrl) | |||
| CalendarBuilder().build(inputStream) | |||
| } catch (t: Throwable) { | |||
| null | |||
| } | |||
| /** | |||
| * The list of iCal [VEvent]s on the [calendar]. | |||
| */ | |||
| val events: Collection<VEvent>? | |||
| get() = | |||
| try { | |||
| val cal = calendar | |||
| var events = cal!!.getComponents<VEvent>("VEVENT") | |||
| val period = Period(DateTime(Date().time), Duration.of(60, ChronoUnit.DAYS)) | |||
| val rule = PeriodRule<VEvent>(period) | |||
| events = events.filter { rule.test(it) } | |||
| events = events.sortedBy { it.startDate } | |||
| events | |||
| } catch (t: Throwable) { | |||
| null | |||
| } | |||
| /** | |||
| * Is the bot going to update its status? | |||
| */ | |||
| var updateStatus: Boolean = true | |||
| /** | |||
| * The commands the bot has. | |||
| */ | |||
| val commands: List<CommandBase> = listOf( | |||
| // SetEventsChannelCommand, | |||
| HelpCommand, | |||
| ChangePrefixCommand, | |||
| UpcomingEventsCommand, | |||
| // PingCommand | |||
| ) | |||
| /** | |||
| * Initializes the bot | |||
| */ | |||
| fun load() { | |||
| // Configuration | |||
| val configFile = File("config.yml") | |||
| if (!configFile.exists()) { | |||
| javaClass.getResourceAsStream("/config.yml")?.let { Files.copy(it, configFile.toPath()) } | |||
| logger.error("No config file found! Default copied to current directory.") | |||
| exitProcess(1) | |||
| } | |||
| val output = yaml.load(FileInputStream(configFile)) as Map<String, Any> | |||
| prefix = output["prefix"].toString() | |||
| token = output["token"].toString() | |||
| updateStatus = output["update-status"]?.toString()?.toBoolean() ?: true | |||
| calendarUrl = output["calendar-url"].toString() | |||
| val logLevel = output["log-level"]?.toString() ?: "INFO" | |||
| val level = Level.toLevel(logLevel, Level.INFO) | |||
| (LoggerFactory.getLogger("ROOT") as Logger).level = level | |||
| // Managers | |||
| IEEEventsGuildSettingsManager.start() | |||
| commandManager = CommandManager(IEEEventsGuildSettingsManager, prefix) { cEvent, t -> | |||
| cEvent.replyError("An unknown error occurred!", { }, { }) | |||
| logger.error("An uncaught exception occurred in a command: $t") | |||
| } | |||
| // Initialize the commands | |||
| commands.forEach { command -> | |||
| commandManager.addCommand(command) | |||
| } | |||
| } | |||
| override fun onEvent(event: GenericEvent) { | |||
| when (event) { | |||
| is ReadyEvent -> { | |||
| StatusService.start() | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,34 @@ | |||
| package xyz.brettb.discord.ieeevents | |||
| import okhttp3.OkHttpClient | |||
| import okhttp3.Request | |||
| import java.io.InputStream | |||
| import java.net.HttpURLConnection | |||
| import java.util.concurrent.TimeUnit | |||
| object Utils { | |||
| fun downloadCalendar(url: String): InputStream { | |||
| val client = OkHttpClient.Builder() | |||
| .connectTimeout(5, TimeUnit.SECONDS) | |||
| .readTimeout(5, TimeUnit.SECONDS) | |||
| .build() | |||
| val request = Request.Builder() | |||
| .url(url) | |||
| .build() | |||
| val response = client.newCall(request).execute() | |||
| val body = response.body | |||
| val resCode = response.code | |||
| if (resCode >= HttpURLConnection.HTTP_OK && | |||
| resCode < HttpURLConnection.HTTP_MULT_CHOICE && | |||
| body != null) { | |||
| return body.byteStream() | |||
| } else { | |||
| throw error("failed to download file") | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| package xyz.brettb.discord.ieeevents.commands | |||
| import net.dv8tion.jda.api.Permission | |||
| import tech.junodevs.discord.kriess.command.Command | |||
| import tech.junodevs.discord.kriess.command.CommandCategory | |||
| import tech.junodevs.discord.kriess.command.CommandEvent | |||
| import tech.junodevs.discord.kriess.exceptions.MissingArgumentException | |||
| import xyz.brettb.discord.ieeevents.logger | |||
| import java.util.concurrent.TimeUnit | |||
| abstract class CommandBase( | |||
| name: String, | |||
| description: String, | |||
| category: CommandCategory, | |||
| aliases: List<String> = listOf(), | |||
| arguments: String = "", | |||
| showInHelp: Boolean = true, | |||
| ownerOnly: Boolean = false, | |||
| val permissions: List<Permission> = listOf(), | |||
| ) : Command(name, aliases, arguments, description, category, showInHelp, ownerOnly) { | |||
| val realUsage: String by lazy { | |||
| if (this.arguments.isEmpty()) name else { | |||
| name + " " + this.arguments.joinToString(" ") { it.usage } | |||
| } | |||
| } | |||
| fun checkPermission(event: CommandEvent): Boolean { | |||
| if (event.isOwner) return true | |||
| if (ownerOnly) return false | |||
| if (!event.member.hasPermission(event.textChannel, permissions)) return false | |||
| return true | |||
| } | |||
| override fun preHandle(event: CommandEvent): Boolean { | |||
| // Check for required arguments! | |||
| try { | |||
| event.arguments | |||
| } catch (t: MissingArgumentException) { | |||
| event.replyError( | |||
| "It looks like the required argument **${t.argument.name}** is missing\n Please check the **usage** and try again.", | |||
| { | |||
| it.delete().queueAfter(5, TimeUnit.SECONDS) | |||
| }) | |||
| return false | |||
| } | |||
| if (!checkPermission(event)) { | |||
| if (ownerOnly) { | |||
| event.replyError( | |||
| "Only the bot owner can run that command!", | |||
| { | |||
| it.delete().queueAfter(5, TimeUnit.SECONDS) | |||
| }) | |||
| } else { | |||
| event.replyError( | |||
| "It looks like you don't have the proper permissions for that command!\n**Needed:** ${ | |||
| permissions.joinToString( | |||
| ", " | |||
| ) { "`${it.getName()}`" } | |||
| }", | |||
| { | |||
| it.delete().queueAfter(5, TimeUnit.SECONDS) | |||
| }) | |||
| } | |||
| return false | |||
| } | |||
| logger.info("${event.command.name} | ${event.author.asTag} [${event.author.id}] | ${event.textChannel.name} [${event.textChannel.id}] | ${event.guild.name} [${event.guild.id}] | ${event.message.contentRaw}") | |||
| return true | |||
| } | |||
| } | |||
| @ -0,0 +1,8 @@ | |||
| package xyz.brettb.discord.ieeevents.commands | |||
| import tech.junodevs.discord.kriess.command.CommandCategory | |||
| object CommandCategories { | |||
| val INFO = CommandCategory("Information") | |||
| val SETTINGS = CommandCategory("Settings") | |||
| } | |||
| @ -0,0 +1,70 @@ | |||
| package xyz.brettb.discord.ieeevents.commands.info | |||
| import net.dv8tion.jda.api.EmbedBuilder | |||
| import net.dv8tion.jda.api.entities.MessageEmbed | |||
| import tech.junodevs.discord.kriess.command.CommandEvent | |||
| import tech.junodevs.discord.kriess.menus.PaginationOptions | |||
| import tech.junodevs.discord.kriess.menus.PaginatorMenu | |||
| import xyz.brettb.discord.ieeevents.Constants | |||
| import xyz.brettb.discord.ieeevents.IEEEventsBot | |||
| import xyz.brettb.discord.ieeevents.commands.CommandBase | |||
| import xyz.brettb.discord.ieeevents.commands.CommandCategories | |||
| import xyz.brettb.discord.ieeevents.data.settings.settings | |||
| import java.util.concurrent.TimeUnit | |||
| object HelpCommand : CommandBase( | |||
| "help", | |||
| "Get some help on the bot", | |||
| CommandCategories.INFO, | |||
| listOf("h"), | |||
| "[command]" | |||
| ) { | |||
| override fun handle(event: CommandEvent) { | |||
| val prefix = event.guild.settings.get().realPrefix | |||
| if (event.arguments.contains("command")) { | |||
| val command = event.arguments.command("command") as CommandBase | |||
| event.reply(EmbedBuilder().run { | |||
| setDescription("IEEEvents Help") | |||
| setColor(Constants.Colors.BLUE) | |||
| val aliases = | |||
| if (command.aliases.isNotEmpty()) command.aliases.joinToString(", ") { "`$it`" } else "**No Aliases**" | |||
| addField( | |||
| command.name, | |||
| "Description: **${command.description}**\nAliases: ${aliases}\nUsage: `${command.realUsage}`", | |||
| false | |||
| ) | |||
| setFooter("Arguments in [] are optional | Requested by: ${event.author.asTag}") | |||
| build() | |||
| }) | |||
| } else { | |||
| val commands = | |||
| event.commandManager.getCommands() | |||
| .filter { it.showInHelp && (it as CommandBase).checkPermission(event) } | |||
| .groupBy { it.category }.toSortedMap(Comparator.comparingInt { it.priority }) | |||
| val fields = commands.map { (category, commands) -> | |||
| MessageEmbed.Field( | |||
| category.name, | |||
| commands.joinToString("\n") { "$prefix${it.name}" }, | |||
| true | |||
| ) | |||
| }.chunked(1) | |||
| PaginatorMenu( | |||
| event.textChannel, | |||
| event.author, | |||
| "IEEEvents Help", | |||
| PaginationOptions(TimeUnit.MINUTES.toMillis(1), embedColor = Constants.Colors.BLUE), | |||
| fields | |||
| ).begin() | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,89 @@ | |||
| package xyz.brettb.discord.ieeevents.commands.info | |||
| import net.dv8tion.jda.api.EmbedBuilder | |||
| import net.dv8tion.jda.api.entities.MessageEmbed | |||
| import net.fortuna.ical4j.model.component.VEvent | |||
| import tech.junodevs.discord.kriess.command.CommandEvent | |||
| import tech.junodevs.discord.kriess.menus.PaginationOptions | |||
| import tech.junodevs.discord.kriess.menus.PaginatorMenu | |||
| import xyz.brettb.discord.ieeevents.Constants | |||
| import xyz.brettb.discord.ieeevents.IEEEventsBot | |||
| import xyz.brettb.discord.ieeevents.commands.CommandBase | |||
| import xyz.brettb.discord.ieeevents.commands.CommandCategories | |||
| import java.time.ZoneId | |||
| import java.time.format.DateTimeFormatter | |||
| import java.util.concurrent.TimeUnit | |||
| object UpcomingEventsCommand : CommandBase( | |||
| "upcoming", | |||
| "Get a list of upcoming events", | |||
| CommandCategories.INFO, | |||
| listOf("uc") | |||
| ) { | |||
| // Date Formatter of pattern: Wed Nov 2 2022 @ 6:00pm | |||
| private val df = DateTimeFormatter.ofPattern("EEE MMM d uuuu '@' hh:mma") | |||
| override fun handle(event: CommandEvent) { | |||
| val events = try { | |||
| IEEEventsBot.events | |||
| } catch (t: Throwable) { | |||
| event.replyError("Failed to retrieve upcoming events!") | |||
| return | |||
| }!! | |||
| if (events.isNotEmpty()) { | |||
| if (events.size > 1) { | |||
| val fields = events.map { calEvent -> | |||
| MessageEmbed.Field( | |||
| calEvent.summary.value.toString(), | |||
| createEventString(calEvent), | |||
| false | |||
| ) | |||
| }.chunked(3) | |||
| PaginatorMenu( | |||
| event.textChannel, | |||
| event.author, | |||
| "IEEE Upcoming Events", | |||
| PaginationOptions(TimeUnit.MINUTES.toMillis(1), embedColor = Constants.Colors.BLUE), | |||
| fields | |||
| ).begin() | |||
| } else { | |||
| val calEvent = events.first() | |||
| event.reply( | |||
| EmbedBuilder() | |||
| .setTitle("IEEE Upcoming Events") | |||
| .addField( | |||
| calEvent.summary.value.toString(), | |||
| createEventString(calEvent), | |||
| false | |||
| ) | |||
| .build() | |||
| ) | |||
| } | |||
| } else { | |||
| event.reply( | |||
| EmbedBuilder() | |||
| .setTitle("IEEE Upcoming Events") | |||
| .setDescription("There are no upcoming events within the next 60 days.") | |||
| .setColor(Constants.Colors.BLUE) | |||
| .build() | |||
| ) | |||
| } | |||
| } | |||
| private fun createEventString(calEvent: VEvent): String { | |||
| var temp = "From **${ | |||
| df.format(calEvent.startDate.date.toInstant().atZone(ZoneId.systemDefault())) | |||
| }** until **${df.format(calEvent.endDate.date.toInstant().atZone(ZoneId.systemDefault()))}**\n" | |||
| temp += calEvent.description.value.toString().substring(0..256 - temp.length) + "...\n" + calEvent.url.value | |||
| return temp | |||
| } | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| package xyz.brettb.discord.ieeevents.commands.settings | |||
| import net.dv8tion.jda.api.Permission | |||
| import tech.junodevs.discord.kriess.command.CommandEvent | |||
| import xyz.brettb.discord.ieeevents.commands.CommandBase | |||
| import xyz.brettb.discord.ieeevents.commands.CommandCategories | |||
| import xyz.brettb.discord.ieeevents.data.settings.IEEEventsGuildSettingsManager | |||
| object ChangePrefixCommand : CommandBase( | |||
| "changeprefix", | |||
| "Change the bot prefix", | |||
| CommandCategories.SETTINGS, | |||
| listOf("cp"), | |||
| "[new_prefix:text]", | |||
| true, | |||
| false, | |||
| listOf(Permission.MANAGE_SERVER) | |||
| ) { | |||
| override fun handle(event: CommandEvent) { | |||
| IEEEventsGuildSettingsManager.editSettings(event.guild) { | |||
| if (event.arguments.contains("new_prefix") && !event.arguments.text("new_prefix").equals("none")) { | |||
| prefix = event.arguments.text("new_prefix") | |||
| event.reply(":white_check_mark: Updated prefix to `$prefix`!") | |||
| } else { | |||
| prefix = null | |||
| event.reply(":white_check_mark: Removed custom prefix, you are now using the default: $realPrefix") | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,28 @@ | |||
| package xyz.brettb.discord.ieeevents.data.settings | |||
| import net.dv8tion.jda.api.entities.Guild | |||
| import net.dv8tion.jda.api.entities.TextChannel | |||
| import tech.junodevs.discord.kriess.providers.GuildSettingsProvider | |||
| import xyz.brettb.discord.ieeevents.IEEEventsBot | |||
| import java.util.concurrent.CompletableFuture | |||
| class IEEEventsGuildSettings(val guildid: Long, var prefix: String? = null, val eventChannelID: Long? = null, var mirrorEvents: Boolean = false) : GuildSettingsProvider { | |||
| override val realPrefix: String | |||
| get() = prefix ?: IEEEventsBot.prefix | |||
| val eventChannel: TextChannel? | |||
| get() = if (eventChannelID != null) IEEEventsBot.JDA.getTextChannelById(eventChannelID) else null | |||
| override fun toMap(): Map<String, Any?> { | |||
| return mapOf( | |||
| "prefix" to prefix, | |||
| "eventChannelID" to eventChannelID, | |||
| "mirrorEvents" to mirrorEvents, | |||
| ) | |||
| } | |||
| } | |||
| val Guild.settings: CompletableFuture<IEEEventsGuildSettings> | |||
| get() = IEEEventsGuildSettingsManager.getSettingsFor(this) | |||
| @ -0,0 +1,20 @@ | |||
| package xyz.brettb.discord.ieeevents.data.settings | |||
| import tech.junodevs.discord.kriess.impl.managers.GuildSettingsManager | |||
| object IEEEventsGuildSettingsManager : GuildSettingsManager<IEEEventsGuildSettings>() { | |||
| override fun createAbsentInstance(guildId: Long): IEEEventsGuildSettings { | |||
| return IEEEventsGuildSettings(guildId) | |||
| } | |||
| override fun createInstance(guildId: Long, properties: Map<String, Any?>): IEEEventsGuildSettings { | |||
| return IEEEventsGuildSettings( | |||
| guildId, | |||
| properties["prefix"] as String?, | |||
| properties["eventChannelID"] as Long?, | |||
| properties["mirrorEvents"] as Boolean? ?: false | |||
| ) | |||
| } | |||
| } | |||
| @ -0,0 +1,25 @@ | |||
| package xyz.brettb.discord.ieeevents.services | |||
| import tech.junodevs.discord.kriess.services.Service | |||
| import xyz.brettb.discord.ieeevents.IEEEventsBot | |||
| import xyz.brettb.discord.ieeevents.data.settings.settings | |||
| import java.util.concurrent.TimeUnit | |||
| object EventsService : Service(TimeUnit.MINUTES.toSeconds(30), 15) { | |||
| override fun execute() { | |||
| val events = IEEEventsBot.events | |||
| IEEEventsBot.JDA.guilds.forEach { guild -> | |||
| if (guild.settings.get().mirrorEvents) { | |||
| // Go through events in events | |||
| /// Ensure each event exists | |||
| //// If not, create + send announcement message | |||
| /// Ensure Description (With URL (UID on last line)) + Location Matches | |||
| //// If not, update | |||
| /// Ensure start + end time match | |||
| //// If not, update | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| package xyz.brettb.discord.ieeevents.services | |||
| import net.dv8tion.jda.api.OnlineStatus | |||
| import net.dv8tion.jda.api.entities.Activity | |||
| import tech.junodevs.discord.kriess.services.Service | |||
| import xyz.brettb.discord.ieeevents.IEEEventsBot | |||
| object StatusService : Service(60, 0) { | |||
| override fun execute() { | |||
| if (IEEEventsBot.updateStatus) { | |||
| val events = IEEEventsBot.events | |||
| IEEEventsBot.JDA.presence.setPresence( | |||
| OnlineStatus.DO_NOT_DISTURB, Activity.watching( | |||
| "over ${events?.size ?: "unknown"} upcoming events" | |||
| ) | |||
| ) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,6 @@ | |||
| prefix: "!" | |||
| token: "12396510498gmj0194c8g1m0394gjm" | |||
| update-status: true | |||
| calendar-url: "icalURL" | |||
| log-level: "INFO" | |||
| @ -0,0 +1,9 @@ | |||
| ical4j.unfolding.relaxed=true | |||
| ical4j.parsing.relaxed=true | |||
| ical4j.validation.relaxed=true | |||
| ical4j.compatibility.outlook=true | |||
| ical4j.compatibility.notes=true | |||
| @ -0,0 +1,28 @@ | |||
| <configuration> | |||
| <!-- Prevent configuration information from printing at startup --> | |||
| <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> | |||
| <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> | |||
| <encoder> | |||
| <pattern>%yellow([%d{HH:mm:ss.SSS}]) %highlight(%-5level) %green([%t]) %cyan(%c{0}) %boldRed(::) %msg%n</pattern> | |||
| </encoder> | |||
| </appender> | |||
| <appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |||
| <file>logs/latest.log</file> | |||
| <immediateFlush>true</immediateFlush> | |||
| <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | |||
| <fileNamePattern>logs/%d{yyyy,aux}/%d{MM,aux}/%d{ddMMyy}.log.gz</fileNamePattern> | |||
| </rollingPolicy> | |||
| <encoder> | |||
| <pattern>[%d{MM.dd.yy HH:mm:ss.SSS}] %-5level [%t] %c :: %msg%n</pattern> | |||
| </encoder> | |||
| </appender> | |||
| <root level="info"> | |||
| <appender-ref ref="Console" /> | |||
| <appender-ref ref="File" /> | |||
| </root> | |||
| </configuration> | |||