1 Commits

Author SHA1 Message Date
Wieland Schöbl
2dfe3f1bd7 Add self-configuration feature 2020-08-17 20:22:27 +02:00
24 changed files with 241 additions and 959 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
servers.json
admin.json
service_channels.json
test.json
*.hprof
/build
/.gradle

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="HytaleUpdateBot:main" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="de.wulkanat" external.system.module.version="1.4.2" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,22 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/HytaleUpdateBot.iml" filepath="$PROJECT_DIR$/.idea/HytaleUpdateBot.iml" />
</modules>
</component>
</project>

206
.idea/workspace.xml generated
View File

@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="1aabf22b-2f57-46ac-9973-367d8668ffd3" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/OwnerCli.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/OwnerCli.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/OwnerCli.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Admin.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Admin.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Channels.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Channels.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Cli.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/AdminCli.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DataIO.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/DataIO.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Main.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/web/SiteWatcher.kt" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -31,11 +35,52 @@
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Source Sets" type="e897c970:GradleViewContributor$SourceSetsNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="build" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="build setup" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="documentation" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="help" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="other" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="verification" type="c8890929:TasksNode$1" />
</path>
</expand>
<select />
</tree_state>
@@ -46,55 +91,44 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Class" />
<option value="Kotlin Class" />
<option value="Kotlin Object" />
<option value="Kotlin File" />
<option value="Class" />
<option value="Kotlin Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="add-twitter-integration" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$PROJECT_DIR$/build.gradle" root0="SKIP_INSPECTION" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="5kk4ojxu" />
</component>
<component name="ProjectId" id="1g2oQiuUv1Bu6ZCW2NSVzB1V6Sc" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/cli" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/build/libs" />
<property name="project.structure.last.edited" value="Modules" />
<property name="project.structure.proportion" value="0.15" />
<property name="project.structure.side.proportion" value="0.2" />
<property name="settings.editor.selected.configurable" value="reference.settingsdialog.project.gradle" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot" />
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\src\main\kotlin\de\wulkanat" />
</key>
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
<recent name="de.wulkanat" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\src\main\kotlin\de\wulkanat\cli" />
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\build\libs" />
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\src" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot" />
<recent name="E:\Projects\Kotlin_Proj\HytaleUpdateBot\src\main\kotlin\de\wulkanat" />
</key>
</component>
<component name="RunManager" selected="Gradle.HytaleUpdateBot [fatJar]">
<configuration name="HytaleUpdateBot [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
@@ -113,9 +147,7 @@
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
<configuration name="HytaleUpdateBot [clean]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
@@ -134,9 +166,7 @@
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
<configuration name="HytaleUpdateBot [fatJar]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
@@ -155,9 +185,7 @@
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
<method v="2" />
</configuration>
<configuration name="HytaleUpdateBot-all-1.0-SNAPSHOT.jar" type="JarApplication" temporary="true">
@@ -179,15 +207,14 @@
</configuration>
<recent_temporary>
<list>
<item itemvalue="Application.MainKt" />
<item itemvalue="JAR Application.HytaleUpdateBot-all-1.0-SNAPSHOT.jar" />
<item itemvalue="Gradle.HytaleUpdateBot [fatJar]" />
<item itemvalue="Gradle.HytaleUpdateBot [clean]" />
<item itemvalue="Kotlin.MainKt" />
<item itemvalue="Gradle.HytaleUpdateBot [build]" />
<item itemvalue="Gradle.HytaleUpdateBot [clean]" />
<item itemvalue="JAR Application.HytaleUpdateBot-all-1.0-SNAPSHOT.jar" />
</list>
</recent_temporary>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="project-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration">
<configuration />
</component>
@@ -220,60 +247,31 @@
<option name="project" value="LOCAL" />
<updated>1597438317540</updated>
</task>
<task id="LOCAL-00004" summary="Add service announcement channel">
<created>1597839954908</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1597839954909</updated>
</task>
<task id="LOCAL-00005" summary="prepare twitter integration">
<created>1601042375685</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1601042375685</updated>
</task>
<task id="LOCAL-00006" summary="fix connection failed message not disappearing">
<created>1601042672099</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1601042672099</updated>
</task>
<option name="localTasksCounter" value="7" />
<option name="localTasksCounter" value="4" />
<servers />
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Add auto publish feature" />
<MESSAGE value="[1.1]" />
<MESSAGE value="Add service announcement channel" />
<MESSAGE value="fix crash on missing permission&#10;add removeInactive command" />
<MESSAGE value="prepare twitter integration" />
<MESSAGE value="fix connection failed message not disappearing" />
<option name="LAST_COMMIT_MESSAGE" value="fix connection failed message not disappearing" />
<option name="LAST_COMMIT_MESSAGE" value="[1.1]" />
</component>
<component name="WindowStateProjectService">
<state x="552" y="179" key="#Project_Structure" timestamp="1601148661909">
<state x="552" y="179" key="#Project_Structure" timestamp="1597687666334">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="552" y="179" key="#Project_Structure/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601148661909" />
<state x="552" y="179" key="#Project_Structure/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597687666334" />
<state x="-1050" y="581" key="#com.intellij.execution.impl.EditConfigurationsDialog" timestamp="1597352463714">
<screen x="-1050" y="105" width="1050" height="1640" />
</state>
<state x="-1050" y="581" key="#com.intellij.execution.impl.EditConfigurationsDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597352463714" />
<state x="640" y="249" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog" timestamp="1597933755909">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="640" y="249" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597933755909" />
<state x="633" y="446" key="#com.intellij.refactoring.move.MoveHandler.SelectRefactoringDialog" timestamp="1597362173063">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="633" y="446" key="#com.intellij.refactoring.move.MoveHandler.SelectRefactoringDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597362173063" />
<state x="690" y="268" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog" timestamp="1597831342920">
<state x="690" y="268" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog" timestamp="1597428556346">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="690" y="268" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597831342920" />
<state x="690" y="268" key="#com.intellij.refactoring.safeDelete.UnsafeUsagesDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597428556346" />
<state x="739" y="173" width="484" height="693" key="#org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog" timestamp="1597362199927">
<screen x="0" y="0" width="1920" height="1040" />
</state>
@@ -282,58 +280,54 @@
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="128" y="270" width="490" height="591" key="#xdebugger.evaluate/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597332665464" />
<state x="569" y="115" key="CommitChangelistDialog2" timestamp="1601042670025">
<state x="569" y="115" key="CommitChangelistDialog2" timestamp="1597438316655">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="569" y="115" key="CommitChangelistDialog2/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601042670025" />
<state x="569" y="115" key="CommitChangelistDialog2/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438316655" />
<state x="740" y="238" key="FileChooserDialogImpl" timestamp="1597605616287">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="740" y="238" key="FileChooserDialogImpl/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597605616287" />
<state width="1877" height="161" key="GridCell.Tab.0.bottom" timestamp="1601059408217">
<state width="1876" height="161" key="GridCell.Tab.0.bottom" timestamp="1597438612599">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.0.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329412" />
<state width="1877" height="161" key="GridCell.Tab.0.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601059408217" />
<state width="1877" height="161" key="GridCell.Tab.0.center" timestamp="1601059408216">
<state width="1876" height="161" key="GridCell.Tab.0.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438612599" />
<state width="1876" height="161" key="GridCell.Tab.0.center" timestamp="1597438612598">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.0.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329411" />
<state width="1877" height="161" key="GridCell.Tab.0.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601059408216" />
<state width="1877" height="161" key="GridCell.Tab.0.left" timestamp="1601059408216">
<state width="1876" height="161" key="GridCell.Tab.0.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438612598" />
<state width="1876" height="161" key="GridCell.Tab.0.left" timestamp="1597438612597">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.0.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329411" />
<state width="1877" height="161" key="GridCell.Tab.0.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601059408216" />
<state width="1877" height="161" key="GridCell.Tab.0.right" timestamp="1601059408216">
<state width="1876" height="161" key="GridCell.Tab.0.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438612597" />
<state width="1876" height="161" key="GridCell.Tab.0.right" timestamp="1597438612599">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.0.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329411" />
<state width="1877" height="161" key="GridCell.Tab.0.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601059408216" />
<state width="1876" height="348" key="GridCell.Tab.1.bottom" timestamp="1597840755247">
<state width="1876" height="161" key="GridCell.Tab.0.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438612599" />
<state width="1006" height="588" key="GridCell.Tab.1.bottom" timestamp="1597366506508">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.1.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329412" />
<state width="1876" height="348" key="GridCell.Tab.1.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597840755247" />
<state width="1876" height="348" key="GridCell.Tab.1.center" timestamp="1597840755246">
<state width="1006" height="588" key="GridCell.Tab.1.bottom/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597366506508" />
<state width="1006" height="588" key="GridCell.Tab.1.center" timestamp="1597366506506">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.1.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329412" />
<state width="1876" height="348" key="GridCell.Tab.1.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597840755246" />
<state width="1876" height="348" key="GridCell.Tab.1.left" timestamp="1597840755246">
<state width="1006" height="588" key="GridCell.Tab.1.center/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597366506506" />
<state width="1006" height="588" key="GridCell.Tab.1.left" timestamp="1597366506505">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.1.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329412" />
<state width="1876" height="348" key="GridCell.Tab.1.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597840755246" />
<state width="1876" height="348" key="GridCell.Tab.1.right" timestamp="1597840755247">
<state width="1006" height="588" key="GridCell.Tab.1.left/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597366506505" />
<state width="1006" height="588" key="GridCell.Tab.1.right" timestamp="1597366506507">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state width="1006" height="588" key="GridCell.Tab.1.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597351329412" />
<state width="1876" height="348" key="GridCell.Tab.1.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597840755247" />
<state x="540" y="255" key="IDE.errors.dialog" timestamp="1601148874155">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="540" y="255" key="IDE.errors.dialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601148874155" />
<state width="1006" height="588" key="GridCell.Tab.1.right/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597366506507" />
<state x="672" y="237" key="MultipleFileMergeDialog" timestamp="1597438068748">
<screen x="0" y="0" width="1920" height="1040" />
</state>
@@ -343,38 +337,24 @@
</state>
<state x="-1040" y="568" key="SettingsEditor/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@-1050.105.1050.1640" timestamp="1597353858648" />
<state x="94" y="257" key="SettingsEditor/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597361509050" />
<state x="552" y="254" key="Vcs.Push.Dialog.v2" timestamp="1601042674727">
<state x="552" y="254" key="Vcs.Push.Dialog.v2" timestamp="1597438319085">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="552" y="254" key="Vcs.Push.Dialog.v2/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601042674727" />
<state x="777" y="434" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2" timestamp="1601042455663">
<screen x="0" y="0" width="1920" height="1040" />
<state x="552" y="254" key="Vcs.Push.Dialog.v2/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597438319085" />
<state x="2582" y="100" key="new project wizard" timestamp="1597605657341">
<screen x="1920" y="-213" width="2560" height="1400" />
</state>
<state x="777" y="434" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1601042455663" />
<state x="497" y="233" key="new project wizard" timestamp="1597841063797">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="497" y="233" key="new project wizard/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597841063797" />
<state x="2582" y="100" key="new project wizard/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@1920.-213.2560.1400" timestamp="1597605657341" />
<state x="616" y="240" key="run.anything.popup" timestamp="1597325088886">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="616" y="240" key="run.anything.popup/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597325088886" />
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup" timestamp="1597702900013">
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup" timestamp="1597363843473">
<screen x="0" y="0" width="1920" height="1040" />
</state>
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597702900013" />
<state x="623" y="225" width="672" height="678" key="search.everywhere.popup/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597363843473" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/src/main/kotlin/de/wulkanat/AdminCli.kt</url>
<line>22</line>
<option name="timeStamp" value="1" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
<watches-manager>
<configuration name="JetRunConfigurationType">
<watch expression="((org.jsoup.nodes.Element.NodeList)((Document)doc).childNodes).get(2)" custom="org.jsoup.nodes.Element.NodeList,org.jsoup.nodes.Document" />

106
README.md
View File

@@ -6,63 +6,81 @@ the bot to your server. Please note that only people with *Administrator* permis
configure it.
You can type `%!info` to get an overview over all available commands.
## Commands
| **Command** | **Arguments** | **Info** |
|------------------|--------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| %!add | | Add current channel to the notified list |
| %!remove | | Remove current channel to the notified list |
| %!publish | on &#124; off | [Community&#124;Partner&#124;Verified only] Auto publish the message if in an announcement channel |
| %!ping | none &#124; everyone &#124; roleName | What role to ping |
| %!setMessage | message | Set a custom message when a blogpost arrives |
| %!resetMessage | | Reset the custom message to none |
| %!serviceChannel | add &#124; remove | Add/remove channel from service notification list |
| %!publishMessage | on &#124; off | [Community&#124;Partner&#124;Verified only] Auto publish the custom message if in an announcement channel |
| %!info | | Show an overview about all channels registered on this server |
| %!report | Your message | Report an issue to the Bot Admin (this will share your user name so they can contact you) |
| %!help | | Show a help dialog with all these commands |
## Self Hosting
Okay, this isn't really meant for you to setup, but if you *really* want to set it up yourself, fine.
Go to the release tab, download the jar, and put it in a folder.
Start the server with `java -jar [server-file-name]` If you put in everything correctly,
the bot should message you on Discord.
*Note:* You need to invite the bot into a server before it can message you.
Run it once (it should crash or print an error), so `admin.json`, `servers.json` and `service_channels.json`
are being created.
Add your Discord ID `adminId` (not name), Bot token `token`, and update frequency `updateMs` to the `admin.json`,
optionally you can add your own messages for when the bot is looking and when it can't reach Hytale Servers.
If you verified that everything works correctly, you can start the server in the background, on Linux that is
`nohup java -jar [server-file-name]`. To stop it you can either type `!stop` in the Admin Console (Discord PM) or
if the bot is unresponsive the the PID of it through `ps -ef` and `kill [pid]`
* first go to the release tab, download the jar, and put it in a folder
* Add two files in the root of the repo, an `admin.json` and a `servers.json`.
Add your Discord ID (not name), Bot token, and update frequency to the `admin.json`:
```json
{
"adminId": 12345678910,
"token": "AOGH@(AKnjsfjiJijaig3ijgG92jaij",
"updateMs":30000
}
```
* add your servers to `servers.json`
```json
[
{
"id": 15050067772322222,
"mentionedRole": "everyone",
"autoPublish":true
},
{
"id": 74050067772325222,
"mentionedRole": null,
"autoPublish":false
},
{
"id": 74050067772325222,
"mentionedRole": "74036067771625222",
"autoPublish":false
}
]
```
* add a `test.json` with the same schema as the `server.json`. When
you enable test mode, the servers from there will be used instead allowing
you to test if it works.
## Compiling yourself
I developed it under Windows, and had some trouble compiling it on Linux. You mileage may vary.
## Admin commands
| **Command** | **Arguments** | **Info** |
|------------------|-------|---------------------|
| !info | | Show all registered channels and servers. |
| !stop | | Stop the server (useful when running in `nohup`) |
| !serviceMessage | message | Send a service message to all registered channels |
| !fakeUpdate | | Cause a fake update (**WARNING**: This will show on **ALL** registered servers) |
| !refreshList | | Refresh servers and service channels from disk (if you manually edit the JSON files) |
| !removeInactive | | Remove inactive channels |
| !help | | Show a help dialog with all these commands |
Start the server with `java -jar [server-file-name]` If you put in everything correctly, the bot should message you on Discord.
### Adding Servers
Please edit the JSON file.
You can force an update by calling
```
%!refreshList
```
### Testing
Switching between test and production files
```
%!testMode
%!fakeUpdate
```
```
%!productionMode
```
**WARNING**: Initiating a fake update is not being cancelled by switching
to production.
### Stop the server from within Discord
```
%!stop
```
### Show servers, channels and roles
```
%!info
```
These commands will only work by private messaging the bot (and will be ignored if they don't
come from the admin registered in the `admin.json`.
These commands will work in every channel, but will be ignored if they don't come from you, however the bot will always respond in a private message.
It will also print errors directly in a Discord private message.
## TODO
Mainly reaction roles for convenience, Twitter integration to either be even faster or to brag how much faster
we were over the official Hytale Twitter.
Mainly reaction roles for convenience, self setup on invite to server, Twitter integration.
## Other

View File

@@ -4,7 +4,7 @@ plugins {
}
group 'de.wulkanat'
version '1.4.2'
version '1.1'
repositories {
mavenCentral()

View File

@@ -13,16 +13,29 @@ object Admin {
val userId: Long
val token: String
val updateMs: Long
val message: String
val offlineMessage: String
var testModeEnabled: Boolean = false
set(value) {
if (field == value)
return
field = value
if (value) {
jda?.presence?.setPresence(Activity.of(Activity.ActivityType.DEFAULT, "Testing mode, hold on..."), true)
} else {
jda?.presence?.setPresence(Activity.watching("for new Blogposts"), false)
}
Channels.channels = Channels.refreshFromDisk()
Admin.info()
}
init {
val admin = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText())
userId = admin.adminId
token = admin.token
updateMs = admin.updateMs
message = admin.watchingMessage
offlineMessage = admin.offlineMessage
}
var jda: JDA? = null
@@ -58,19 +71,12 @@ object Admin {
)
}
fun error(msg: String, error: String, author: User? = null) {
fun error(msg: String, error: String) {
sendDevMessage(
EmbedBuilder()
.setTitle(msg)
.setDescription(error)
.setColor(Color.RED)
.run {
if (author == null) {
this
} else {
this.setAuthor(author.asTag, author.avatarUrl, author.avatarUrl)
}
}
.build()
, "$msg\n\n${error}"
)
@@ -101,12 +107,7 @@ object Admin {
sendDevMessage(
EmbedBuilder()
.setTitle("Now watching for new Hytale Blogposts every ${updateMs / 1000}s")
.setDescription("""
${Channels.getServerNames().joinToString("\n")}
**_Service Channels_**
${Channels.getServiceChannelServers().joinToString("\n")}
""".trimIndent())
.setDescription(Channels.getServerNames().joinToString("\n"))
.setColor(Color.GREEN)
.build(),
"Now watching for new Hytale BlogPosts"
@@ -128,7 +129,7 @@ object Admin {
.sendMessage(messageEmbed).complete()
}
fun sendDevMessage(messageEmbed: MessageEmbed, fallback: String) {
private fun sendDevMessage(messageEmbed: MessageEmbed, fallback: String) {
val devChannel = admin?.openPrivateChannel() ?: kotlin.run {
kotlin.io.println(fallback)
return

View File

@@ -1,26 +1,24 @@
package de.wulkanat
import de.wulkanat.model.BlogPostPreview
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import de.wulkanat.web.SiteWatcher
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.events.ExceptionEvent
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent
import java.awt.Color
import kotlin.system.exitProcess
class AdminCli : ListenerAdapter() {
val prefix = "!"
override fun onPrivateMessageReceived(event: PrivateMessageReceivedEvent) {
val msg = event.message.contentRaw
if (event.author.idLong != Admin.userId ||
!msg.startsWith(prefix)
!msg.startsWith("!")
) {
return
}
val command = Regex("[^\\s`]+|`[^`]*`").findAll(msg.removePrefix("!")).toList()
val command = msg.removePrefix("!").split(Regex("\\s+"))
when (command[0].value) {
when (command[0]) {
"stop" -> exitProcess(1)
"fakeUpdate" -> {
SiteWatcher.newestBlog = BlogPostPreview(
@@ -37,54 +35,15 @@ class AdminCli : ListenerAdapter() {
"info" -> {
Admin.info()
}
"serviceMessage" -> {
if (command.size != 3) {
Admin.println("Enclose message and title in backticks (`)")
} else {
Channels.sendServiceMessage(command[1].value.trim('`'), command[2].value.trim('`'))
}
}
"refreshList" -> {
Channels.channels = Channels.refreshChannelsFromDisk()
Channels.serviceChannels = Channels.refreshServiceChannelsFromDisk()
Channels.channels = Channels.refreshFromDisk()
Admin.info()
}
"removeInactive" -> {
Channels.channels.removeAll { channel ->
Channels.testServerId(channel.id) ?: run {
Admin.println("Removed ${channel.id}")
null
} == null
}
Admin.info()
Channels.saveChannels()
"testMode" -> {
Admin.testModeEnabled = true
}
"help" -> {
event.message.channel.sendMessage(
EmbedBuilder()
.setTitle("Help")
.setColor(Color.YELLOW)
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.setDescription(
"""
**${prefix}stop**
Stop the bot
**${prefix}fakeUpdate**
Post a fake update to every registered channel (can be used if bot missed the update)
**${prefix}info**
Show an overview over all registered channels
**${prefix}serviceMessage [title] [message]**
Show a service message (update info etc) to all registered service channels
**${prefix}refreshList**
Refresh server list from disk
**${prefix}removeInactive**
Remove inactive channels
**${prefix}help**
Show this message
""".trimIndent()
)
.build()
).queue()
"productionMode" -> {
Admin.testModeEnabled = false
}
}
}

View File

@@ -1,88 +1,46 @@
package de.wulkanat
import de.wulkanat.extensions.crosspost
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.MessageEmbed
import net.dv8tion.jda.api.entities.TextChannel
import java.awt.Color
object Channels {
var jda: JDA? = null
val json = Json(JsonConfiguration.Stable)
/**
* List of (ServerID, ChannelID)
*/
var channels: MutableList<DiscordChannel> = refreshChannelsFromDisk()
var serviceChannels: MutableList<ServiceChannel> = refreshServiceChannelsFromDisk()
var channels: MutableList<DiscordChannel> = refreshFromDisk()
fun sentToAll(messageEmbed: MessageEmbed) {
if (jda == null)
return
for (channel_pair in channels) {
try {
val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
val customMessage = channel_pair.message?.message ?: ""
val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
if (channel_pair.mentionedRole != null) {
val message = if (channel_pair.mentionedRole == "everyone") {
"@everyone $customMessage"
} else {
"<@&${channel_pair.mentionedRole}> $customMessage"
}
channel.sendMessage(message).queue {
if (channel_pair.message?.pushAnnouncement == true) {
it.crosspost().queue()
}
}
} else if (channel_pair.message != null) {
channel.sendMessage(customMessage).queue {
if (channel_pair.message?.pushAnnouncement == true) {
it.crosspost().queue()
}
}
if (channel_pair.mentionedRole != null) {
val message = if (channel_pair.mentionedRole == "everyone") {
"New Blogpost @everyone"
} else {
"New Blogpost <@&${channel_pair.mentionedRole}>"
}
channel.sendMessage(messageEmbed).queue {
if (channel_pair.autoPublish) {
it.crosspost().queue()
}
channel.sendMessage(message).queue()
}
channel.sendMessage(messageEmbed).queue {
if (channel_pair.autoPublish) {
it.crosspost().queue()
}
} catch (e: Exception) {
Admin.error("Error in server ${channel_pair.id}", e.message ?: e.localizedMessage)
}
}
}
fun sendServiceMessage(title: String, message: String) {
val serviceMessage = EmbedBuilder()
.setTitle(title)
.setDescription(message)
.setColor(Color.WHITE)
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.setFooter("This was sent by a human.")
.build()
for (channelInfo in serviceChannels) {
val channel = jda!!.getTextChannelById(channelInfo.id)
channel?.sendMessage(serviceMessage)?.queue()
}
Admin.println("Service message distributed to ${serviceChannels.size} channels.")
Admin.sendDevMessage(serviceMessage, """
***************
SERVICE MESSAGE
$title
-------
$message
***************
""".trimIndent())
}
fun checkEveryonePermission() {
for (channel_pair in channels) {
val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
@@ -98,15 +56,13 @@ object Channels {
}
}
fun refreshChannelsFromDisk(): MutableList<DiscordChannel> {
fun refreshFromDisk(): MutableList<DiscordChannel> {
return json.parse(
DiscordChannel.serializer().list, (SERVERS_FILE).readText()
).toMutableList()
}
fun refreshServiceChannelsFromDisk(): MutableList<ServiceChannel> {
return json.parse(
ServiceChannel.serializer().list, (SERVICE_CHANNELS_FILE).readText()
DiscordChannel.serializer().list, (if (Admin.testModeEnabled) {
TEST_FILE
} else {
SERVERS_FILE
}).readText()
).toMutableList()
}
@@ -114,7 +70,7 @@ object Channels {
if (jda == null)
return listOf()
return channels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
return channels.filter { server != null && (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
val channel = jda!!.getTextChannelById(it.id)
if (channel == null) {
Admin.warning("Channel ${it.id} is no longer active!")
@@ -126,23 +82,7 @@ object Channels {
"everyone" -> " @everyone"
else -> " @${channel.guild.getRoleById(it.mentionedRole ?: "")?.name}"
}
val publish = if (it.autoPublish) " (publish)" else ""
"**${channel.guild.name}** #${channel.name}${role}${publish}${if (it.message == null) {
""
} else {
"\n*${it.message!!.message}*${if (it.message!!.pushAnnouncement) " (publish)" else ""}"
}
}"
}
}
fun getServiceChannelServers(server: Long? = null): List<String> {
if (jda == null)
return listOf()
return serviceChannels.filter { server == null || (jda!!.getTextChannelById(it.id)?.guild?.idLong == server) }.map {
val channel = jda!!.getTextChannelById(it.id)
"**${channel?.guild?.name ?: it.id}** #${channel?.name ?: "(inactive)"}"
"**${channel.guild.name}**\n#${channel.name}${role}"
}
}
@@ -167,11 +107,5 @@ object Channels {
channels
)
)
SERVICE_CHANNELS_FILE.writeText(
json.stringify(
ServiceChannel.serializer().list,
serviceChannels
)
)
}
}

View File

@@ -1,43 +1,22 @@
package de.wulkanat
import de.wulkanat.extensions.ensureExists
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import java.io.File
@Serializable
data class DiscordChannel(
val id: Long,
var mentionedRole: String? = null,
var autoPublish: Boolean = false,
var message: CustomMessage? = null
)
@Serializable
data class ServiceChannel(
val id: Long
)
@Serializable
data class CustomMessage(
var message: String,
var pushAnnouncement: Boolean = false
var autoPublish: Boolean = false
)
@Serializable
data class AdminFile(
val adminId: Long = 12345,
val token: String = "12345",
val updateMs: Long = 30000,
val watchingMessage: String = "for new Blogposts",
val offlineMessage: String = "CONNECTION FAILED"
val adminId: Long,
val token: String,
val updateMs: Long
)
val json = Json(JsonConfiguration.Stable)
val SERVERS_FILE = File("servers.json").ensureExists(json.stringify(DiscordChannel.serializer().list, listOf()))
val SERVICE_CHANNELS_FILE =
File("service_channels.json").ensureExists(json.stringify(ServiceChannel.serializer().list, listOf()))
val ADMIN_FILE = File("admin.json").ensureExists(json.stringify(AdminFile.serializer(), AdminFile()))
val SERVERS_FILE = File("servers.json")
val TEST_FILE = File("test.json")
val ADMIN_FILE = File("admin.json")

View File

@@ -1,15 +0,0 @@
package de.wulkanat
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.entities.Activity
object DiscordRpc {
var jda: JDA? = null
fun updatePresence(available: Boolean) {
jda ?: return
jda!!.presence.activity = Activity.watching(if (available) Admin.message else Admin.offlineMessage)
jda!!.presence.isIdle = !available
}
}

View File

@@ -10,7 +10,7 @@ fun main() {
val builder = JDABuilder.createLight(
Admin.token,
GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
.setActivity(Activity.watching(Admin.message))
.setActivity(Activity.watching("for new Blogposts"))
.build()
builder.addEventListener(AdminCli())
@@ -20,7 +20,6 @@ fun main() {
Channels.jda = builder
Admin.jda = builder
DiscordRpc.jda = builder
Admin.info()
Runtime.getRuntime().addShutdownHook(object : Thread() {

View File

@@ -1,103 +1,12 @@
package de.wulkanat
import de.wulkanat.cli.Cli
import de.wulkanat.cli.makeCli
import net.dv8tion.jda.api.events.message.priv.PrivateMessageReceivedEvent
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.hooks.ListenerAdapter
import java.awt.Color
class OwnerCli : ListenerAdapter() {
private val cli: Cli<PrivateMessageReceivedEvent> = makeCli(prefix = "!") {
command name "add" does "Add this channel to the notified list" through ::OwnerCliStuff.addChannel
command name "remove" does "Remove this channel to the notified list" through removeChannel
command name "publish" with { required literal argument with "on" or "off" } does
"[Community|Partner|Verified only] Auto publish the message if in an announcement channel" through publish
command name "ping" with { required string argument } does "What role to ping" through ping
command name "setMessage" with { required string argument } does "Set a custom message to show" through setMessage
}
}
object OwnerCliStuff {
private fun addChannel(_required: List<String>, _optional: MutableMap<String, String>, event: PrivateMessageReceivedEvent) {
val result = Channels.addChannel(event.channel.idLong, null)
if (result == null) {
event.message.channel.sendMessage("Already added.").queue()
} else {
event.message.channel.sendMessage("Added.").queue()
Admin.info()
}
}
private val removeChannel =
{ _: List<String>, _: MutableMap<String, String>, event: PrivateMessageReceivedEvent ->
val result = Channels.channels.removeAll { it.id == event.channel.idLong }
Channels.saveChannels()
if (result) {
event.message.channel.sendMessage("Removed.").queue()
} else {
event.message.channel.sendMessage("This channel is not registered.").queue()
}
}
private val publish =
publish@{ required: List<String>, _: MutableMap<String, String>, event: PrivateMessageReceivedEvent ->
val channel = Channels.channels.find { it.id == event.channel.idLong } ?: run {
event.message.channel.sendMessage("Channel not registered.").queue()
return@publish
}
channel.autoPublish = required.first() == "on"
Channels.saveChannels()
event.message.channel.sendMessage("Auto publish is now ${required.first()}").queue()
}
private val ping =
ping@{ required: List<String>, _: MutableMap<String, String>, event: PrivateMessageReceivedEvent ->
val channel = Channels.channels.find { it.id == event.channel.idLong } ?: run {
event.message.channel.sendMessage("Channel is not registered.").queue()
return@ping
}
val roleName = required.first()
val role = event.message.guild.getRolesByName(required.first(), false).firstOrNull()
channel.mentionedRole = when {
roleName == "everyone" -> {
event.message.channel.sendMessage("Now pinging $roleName.").queue()
roleName
}
roleName == "none" -> {
event.message.channel.sendMessage("Now pinging $roleName.").queue()
null
}
role != null -> {
event.message.channel.sendMessage("Now pinging ${role.name}").queue()
role.id
}
else -> {
event.message.channel.sendMessage("Unknown role.").queue()
channel.mentionedRole
}
}
Channels.saveChannels()
}
private val setMessage =
setMessage@{ required: List<String>, _: MutableMap<String, String>, event: PrivateMessageReceivedEvent ->
val result = Channels.channels.find { it.id == event.channel.channelId } ?: run {
event.message.channel.sendMessage("Channel is not registered.").queue()
return@setMessage
}
val message = required.first()
result.message = CustomMessage(message)
Channels.saveChannels()
event.message.channel.sendMessage("Set `$message` as message.").queue()
}
}
/*class OwnerCli2 : ListenerAdapter() {
private val prefix = "%!"
override fun onMessageReceived(event: MessageReceivedEvent) {
@@ -136,12 +45,12 @@ object OwnerCliStuff {
result.autoPublish = command[1] == "on"
Channels.saveChannels()
event.message.channel.sendMessage("Auto publish is now ${command[1]}").queue()
event.message.channel.sendMessage("Auto publish is now on").queue()
} else {
event.message.channel.sendMessage("Usage: `${prefix}publish [on|off]`")
}
} else {
event.message.channel.sendMessage("Channel not registered.").queue()
event.message.channel.sendMessage("Added.").queue()
}
}
"ping" -> {
@@ -175,132 +84,36 @@ object OwnerCliStuff {
event.message.channel.sendMessage("Channel is not registered.").queue()
}
}
"setMessage" -> {
val result = Channels.channels.find { it.id == channelId }
if (result != null) {
if (command.size > 1) {
val message = event.message.contentRaw.removePrefix("${prefix}setMessage").trim()
result.message = CustomMessage(message)
Channels.saveChannels()
event.message.channel.sendMessage("Set `$message` as message.").queue()
} else {
event.message.channel.sendMessage("Usage: `${prefix}setMessage [message]`")
}
} else {
event.message.channel.sendMessage("Channel is not registered.").queue()
}
}
"resetMessage" -> {
val result = Channels.channels.find { it.id == channelId }
if (result != null) {
result.message = null
Channels.saveChannels()
event.message.channel.sendMessage("Reset to no message.").queue()
} else {
event.message.channel.sendMessage("Channel is not registered.").queue()
}
}
"serviceChannel" -> {
if (command.size > 1 && listOf("add", "remove").contains(command[1])) {
if (command[1] == "add") {
if (Channels.serviceChannels.find { it.id == channelId } != null) {
event.message.channel.sendMessage("Already a service channel.").queue()
} else {
Channels.serviceChannels.add(ServiceChannel(channelId))
Channels.saveChannels()
event.message.channel.sendMessage("Added as service channel.").queue()
}
} else {
event.message.channel.sendMessage(
if (Channels.serviceChannels.removeAll { it.id == channelId }) "Channel removed."
else "Not a service channel."
).queue()
}
Channels.saveChannels()
} else {
event.message.channel.sendMessage("Usage: `${prefix}serviceChannel [add|remove]`")
}
}
"publishMessage" -> {
val result = Channels.channels.find { it.id == channelId }
if (result != null) {
if (result.message != null) {
if (command.size > 1 && listOf("on", "off").contains(command[1])) {
result.message?.pushAnnouncement = command[1] == "on"
Channels.saveChannels()
event.message.channel.sendMessage("Auto publish (message) is now ${command[1]}").queue()
} else {
event.message.channel.sendMessage("Usage: `${prefix}publishMessage [on|off]`")
}
} else {
event.message.channel.sendMessage("Channel has no custom message.").queue()
}
} else {
event.message.channel.sendMessage("Channel not registered.").queue()
}
}
"info" -> {
event.message.channel.sendMessage(
EmbedBuilder()
.setTitle("Server overview")
.setColor(Color.GREEN)
.setDescription("""
${Channels.getServerNames(event.message.guild.idLong).joinToString("\n")}
**_Service Channels_**
${Channels.getServiceChannelServers(event.message.guild.idLong).joinToString("\n")}
""".trimIndent())
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.build()
).queue()
}
"report" -> {
val errorReport = event.message.contentRaw.removePrefix("${prefix}report")
Admin.error(event.message.guild.name, errorReport, event.author)
event.message.channel.sendMessage(
EmbedBuilder()
.setTitle("Error Report Received")
.setColor(Color.RED)
.setDescription(errorReport)
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.build()
).queue()
event.message.channel.sendMessage(EmbedBuilder()
.setTitle("Server overview")
.setColor(Color.GREEN)
.setDescription(Channels.getServerNames(event.message.guild.idLong).joinToString("\n"))
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.build()).queue()
}
"help" -> {
event.message.channel.sendMessage(
EmbedBuilder()
.setTitle("Help")
.setColor(Color.YELLOW)
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.setDescription(
"""
event.message.channel.sendMessage(EmbedBuilder()
.setTitle("Help")
.setColor(Color.YELLOW)
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
.setDescription(
"""
**${prefix}add**
Add this channel to the notified list
**${prefix}serviceChannel [add|remove]**
Add or remove this channel to receive service message from the bot developer (recommended)
**${prefix}remove**
Remove this channel to the notified list
**${prefix}publish [on|off]**
[Community|Partner|Verified only] Auto publish the message if in an announcement channel
**${prefix}ping [none|everyone|roleName]**
What role to ping
**${prefix}setMessage [message]**
Set a custom message to show
**${prefix}resetMessage**
Reset the message
**${prefix}info**
Show an overview about all channels registered on this server
**${prefix}report**
Report an issue to the Bot Admin (this will share your user name so they can contact you)
**${prefix}help**
Show this message
""".trimIndent()
)
.build()
).queue()
""".trimIndent())
.build()).queue()
}
}
}*
}*/
}
}

View File

@@ -1,18 +0,0 @@
package de.wulkanat.cli
enum class ArgumentType(val match: Regex, val stringName: String) {
INT(Regex("\\d+"), "int"),
FLOAT(Regex("\\d+(?:.\\d+)?"), "float"),
STRING(Regex("[\\s\\S]+"), "string"),
BOOLEAN(Regex("true|false"), "bool"),
LITERAL(Regex("[\\s\\S]+"), "literal"),
EXISTS(Regex("[\\s\\S]+"), "existence");
fun usage(literals: Map<String, List<String>>, name: String): String {
return when (this) {
LITERAL -> "${literals[name]?.joinToString(separator = "|")}"
EXISTS -> ""
else -> stringName
}
}
}

View File

@@ -1,216 +0,0 @@
package de.wulkanat.cli
class Cli<T>(var prefix: String = ".") {
val commands = mutableMapOf<String, Command<T>>()
fun parse(
command: String,
passThrough: T,
helpMessage: (Cli<T>) -> Unit = {},
commandMisuse: (Command<T>, String) -> Unit = { _, _ -> }
): Boolean? {
if (!command.startsWith(prefix)) return false // not a command
val msg =
Regex("[^\\s`]+|`[^`]*`").findAll(command.removePrefix(prefix)).toList().map { it.value }
if (msg[0] == "help") {
helpMessage(this)
return true
}
val realCommand = commands[msg[0]] ?: return false // command not found
val (required, optional) = realCommand.arguments
if (msg.size < required.list.size + 1) {
commandMisuse(realCommand, "Too few arguments!")
return null
}
val requiredOut: MutableList<String> = mutableListOf()
val optionalOut: MutableMap<String, String> = mutableMapOf()
for (i in 1..required.list.size) {
val (name, type) = required.list[i - 1]
requiredOut.add(
when (type) {
ArgumentType.LITERAL -> required.literals[name]?.find { it == msg[i] }?.toString()
else -> type.match.matchEntire(msg[i])?.value
} ?: kotlin.run {
commandMisuse(realCommand, "Argument '${msg[i]}' is not of type ${type.stringName}!")
return@parse null
}
)
}
var i = required.list.size + 1
while (i < required.list.size + 1) {
val key = optional.shorts[msg[i]] ?: msg[i]
val value = optional.list[optional.shorts[msg[i]] ?: msg[i]] ?: kotlin.run {
commandMisuse(realCommand, "Unknown optional argument '$key'")
return@parse null
}
optionalOut[key] = when (value) {
ArgumentType.LITERAL -> optional.literals[key]?.find { it == msg[i] }?.toString()
else -> value.match.matchEntire(msg[i])?.value
} ?: kotlin.run {
commandMisuse(realCommand, "Argument '$key' is not of type ${value.stringName}!")
return@parse null
}
i += if (value == ArgumentType.EXISTS) 1 else 2
}
realCommand.action(requiredOut, optionalOut, passThrough)
return true // success
}
fun usage(): String {
return commands.map { "$prefix${it.value.usage()}" }.joinToString("\n")
}
infix fun prefix(func: Cli<T>.() -> Unit): Cli<T> {
func()
return this
}
inner class CommandBuilder {
infix fun name(name: String): CommandBuilder2 {
return CommandBuilder2(name)
}
}
inner class CommandBuilder2(val name: String) {
val argumentBuilder = ArgumentBuilder()
var descriptionLocal = ""
infix fun does(description: String): DoesHelper {
descriptionLocal = description
return DoesHelper()
}
inner class DoesHelper {
infix fun through(action: (required: List<String>, optional: MutableMap<String, String>, passthrough: T) -> Unit): Command<T> {
return Command(name, descriptionLocal, action, argumentBuilder).also { commands[name] = it }
}
}
infix fun with(action: ArgumentBuilder.() -> Unit): CommandBuilder2 {
argumentBuilder.action()
return this
}
inner class ArgumentBuilder {
val required = RequiredArgHelper()
val optional = OptionalArgHelper()
operator fun component1() = required
operator fun component2() = optional
inner class RequiredArgHelper {
val list: MutableList<Pair<String, ArgumentType>> = mutableListOf()
val literals: MutableMap<String, MutableList<String>> = mutableMapOf()
infix fun int(name: String) {
list.add(Pair(name, ArgumentType.INT))
}
infix fun float(name: String) {
list.add(Pair(name, ArgumentType.FLOAT))
}
infix fun string(name: String) {
list.add(Pair(name, ArgumentType.STRING))
}
infix fun literal(name: String): LiteralHelper {
list.add(Pair(name, ArgumentType.LITERAL))
return LiteralHelper(name)
}
infix fun bool(name: String) {
list.add(Pair(name, ArgumentType.BOOLEAN))
}
inner class LiteralHelper(val name: String) {
infix fun with(literalsList: String): LiteralHelperHelper {
val list = mutableListOf(literalsList)
literals[name] = list
return LiteralHelperHelper(list)
}
inner class LiteralHelperHelper(private val listListList: MutableList<String>) {
infix fun or(other: String): LiteralHelperHelper {
listListList.add(other)
return this
}
}
}
}
inner class OptionalArgHelper {
val list: MutableMap<String, ArgumentType> = mutableMapOf()
val shorts: MutableMap<String, String> = mutableMapOf()
val literals: MutableMap<String, List<String>> = mutableMapOf()
inner class ShortsHelper(val name: String, val shortsMap: MutableMap<String, String>) {
infix fun short(shortName: String) {
shortsMap[shortName] = name
}
}
infix fun int(name: String): ShortsHelper {
list[name] = ArgumentType.INT
return ShortsHelper(name, shorts)
}
infix fun float(name: String): ShortsHelper {
list[name] = ArgumentType.FLOAT
return ShortsHelper(name, shorts)
}
infix fun string(name: String): ShortsHelper {
list[name] = ArgumentType.STRING
return ShortsHelper(name, shorts)
}
infix fun bool(name: String): ShortsHelper {
list[name] = ArgumentType.BOOLEAN
return ShortsHelper(name, shorts)
}
infix fun literal(name: String): LiteralHelper {
list[name] = ArgumentType.LITERAL
return LiteralHelper(name)
}
infix fun existence(name: String): ShortsHelper {
list[name] = ArgumentType.EXISTS
return ShortsHelper(name, shorts)
}
inner class LiteralHelper(val name: String) {
infix fun with(literalsList: String): LiteralHelperHelper {
val list = mutableListOf<String>()
literals[name] = list
return LiteralHelperHelper(list)
}
inner class LiteralHelperHelper(private val listListList: MutableList<String>) {
infix fun or(other: String): LiteralHelperHelper {
listListList.add(other)
return this
}
}
}
}
}
}
val argument = "REQUIRED_TYPE"
val command = CommandBuilder()
val nothing: (List<String>, MutableMap<String, String>, T) -> Unit = { _, _, _ -> }
}

View File

@@ -1,52 +0,0 @@
package de.wulkanat.cli
import net.dv8tion.jda.api.EmbedBuilder
import net.dv8tion.jda.api.entities.MessageEmbed
fun <T> Command<T>.discordUsage(): String {
return "${name}_ ${arguments.required.list
.joinToString(separator = " ") {
"**[**${it.first/*it.second.discordUsage(
arguments.required.literals,
it.first
)*/}**]**"
}} ${arguments.optional.list
.map {
"--${it.key}${arguments.optional.shorts[name]?.let { short -> " _-${short}_ " } ?: ""
}${if (it.value == ArgumentType.EXISTS) "" else " **<**${it.value.stringName}**>**"}"
}
.joinToString(separator = " ")}"
}
fun ArgumentType.discordUsage(literals: Map<String, List<String>>, name: String): String {
return when (this) {
ArgumentType.LITERAL -> "${literals[name]?.joinToString(separator = "**|**")}"
ArgumentType.EXISTS -> ""
else -> stringName
}
}
fun <T> Command<T>.discordUsageEmbed(footer: String?): MessageEmbed {
return EmbedBuilder()
.setTitle("Usage:")
.setDescription("_${discordUsage()}")
.also { builder -> footer?.let { builder.setFooter(footer) } }
.build()
}
fun <T> Cli<T>.discordUsage(): String {
return commands.map { "_$prefix${it.value.discordUsage()}" }.joinToString("\n")
}
fun <T> Cli<T>.discordUsageEmbed(): MessageEmbed {
return EmbedBuilder()
.setTitle("Help")
.also {
commands.map { Pair(it.value.description, "_$prefix${it.value.discordUsage()}") }
.forEach { (title, description) ->
it.addField(title, description, false)
}
}
.setFooter("Commands are case-sensitive.")
.build()
}

View File

@@ -1,7 +0,0 @@
package de.wulkanat.cli
fun <T>makeCli(prefix: String = "!", func: Cli<T>.() -> Unit): Cli<T> {
val cli = Cli<T>(prefix)
cli.func()
return cli
}

View File

@@ -1,23 +0,0 @@
package de.wulkanat.cli
class Command<T>(
val name: String,
val description: String,
val action: (required: List<String>, optional: MutableMap<String, String>, passthrough: T) -> Unit,
val arguments: Cli<T>.CommandBuilder2.ArgumentBuilder
) {
fun usage(): String {
return "$name ${arguments.required.list
.joinToString(separator = " ") {
"[${it.second.usage(
arguments.required.literals,
it.first
)}]"
}} ${arguments.optional.list
.map {
"--${it.key}${arguments.optional.shorts[name]?.let { short -> " -$short " } ?: ""
}${if (it.value == ArgumentType.EXISTS) "" else " <${it.value.stringName}>"}"
}
.joinToString(separator = " ")}"
}
}

View File

@@ -1,11 +0,0 @@
package de.wulkanat.extensions
import java.io.File
fun File.ensureExists(defaultText: String? = null): File {
if (!this.exists()) {
this.createNewFile()
this.writeText(defaultText ?: return this)
}
return this
}

View File

@@ -1,7 +1,6 @@
package de.wulkanat.web
import de.wulkanat.Admin
import de.wulkanat.DiscordRpc
import de.wulkanat.model.BlogPostPreview
import org.jsoup.Jsoup
import java.io.IOException
@@ -9,7 +8,6 @@ import java.io.IOException
object SiteWatcher {
private const val BLOG_INDEX_URL = "https://www.hytale.com/news"
var newestBlog: BlogPostPreview? = null
private var siteOnline = false
fun hasNewBlogPost(): Boolean {
try {
@@ -28,17 +26,10 @@ object SiteWatcher {
}
} catch (e: IOException) {
Admin.error("Connection to Hytale Server failed", e.message ?: e.localizedMessage)
siteOnline = false
DiscordRpc.updatePresence(siteOnline)
return false
}
if (!siteOnline) {
siteOnline = true
DiscordRpc.updatePresence(siteOnline)
}
return true
}
}

View File

@@ -1,8 +0,0 @@
{
"env": "dev",
"accessToken": "1075173710557011968-OfPSjYUF6IDYtOl8yeo1x1EzXVlZWD",
"accessTokenSecret": "ikcLkku0lSNMVY7kgWcj7j7tfk3IHpHUYIkvJACp0zZXh",
"apiKey": "8rm4wAHVLXYauBbRDKAVro0kw",
"apiSecretKey": "vPZgiPxwqqHZXQGMPyxyuQrg8y45t1fAlOar9DpVlPa10JRAfC",
"bearerToken": "AAAAAAAAAAAAAAAAAAAAAJNnHwEAAAAAKVRDEbr2dzzum2wswMGOyJOQHJw%3DgniWSdhaXgcey4XBHQckZSVVXtP6y83wAp0sxnSp3CwbWpXoA3"
}