mirror of
https://github.com/HMCore/Orbot.git
synced 2025-12-12 13:56:18 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e2186026c | ||
|
|
7ff789afd3 | ||
|
|
c9acfdc79e | ||
|
|
3d73d50a47 | ||
|
|
e6eaabaa1a | ||
|
|
1f3ebc42e6 | ||
|
|
43ae59b3e5 | ||
|
|
5511d1c5ac | ||
|
|
cd5321af75 | ||
|
|
c508b6e854 | ||
|
|
cfad5c0611 | ||
|
|
126e630a80 | ||
|
|
9f5a9734bc | ||
|
|
310dcfecd5 | ||
|
|
27a1e5aa7f | ||
|
|
862df48e5f | ||
|
|
3d06de11dd | ||
|
|
163b201828 | ||
|
|
68ed19db7a | ||
|
|
cc7dc8d9e4 | ||
|
|
d3d7b234a7 | ||
|
|
712ff091af | ||
|
|
8e323a8446 | ||
|
|
ccb074d81d | ||
|
|
91e6552772 | ||
|
|
f23a4d9ce5 | ||
|
|
fa00466eb0 | ||
|
|
0c123a6567 | ||
|
|
7b29417678 | ||
|
|
77f5733144 | ||
|
|
7f207674ba | ||
|
|
f0f6015d6f | ||
|
|
288e56f035 | ||
|
|
fcaa8377c1 | ||
|
|
4c4e4dc992 | ||
|
|
8b98d4ba3c | ||
|
|
0877883e3c | ||
|
|
a78c2343da | ||
|
|
490a5dcd41 | ||
|
|
5969a2f221 | ||
|
|
e58f4dc0c5 | ||
|
|
2c7797c112 | ||
|
|
7bf483ab32 | ||
|
|
2741206977 | ||
|
|
d15fb92acf |
29
.github/workflows/gradle.yml
vendored
Normal file
29
.github/workflows/gradle.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# This workflow will build a Java project with Gradle
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Java CI with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,10 @@
|
||||
servers.json
|
||||
admin.json
|
||||
service_channels.json
|
||||
webhooks.json
|
||||
blog_state.json
|
||||
jobs_state.json
|
||||
*.hprof
|
||||
/build
|
||||
/.gradle
|
||||
/.gradle
|
||||
.idea/
|
||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
HytaleUpdateBot
|
||||
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/dictionaries/wulkanat.xml
generated
Normal file
9
.idea/dictionaries/wulkanat.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="wulkanat">
|
||||
<words>
|
||||
<w>crosspost</w>
|
||||
<w>guilded</w>
|
||||
<w>hytale</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
5
.idea/jarRepositories.xml
generated
5
.idea/jarRepositories.xml
generated
@@ -21,5 +21,10 @@
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://m2.dv8tion.net/releases" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
65
.idea/libraries-with-intellij-classes.xml
generated
Normal file
65
.idea/libraries-with-intellij-classes.xml
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="libraries-with-intellij-classes">
|
||||
<option name="intellijApiContainingLibraries">
|
||||
<list>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="ideaIU" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.idea" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="ideaIU" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="ideaIC" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.idea" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="ideaIC" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="pycharmPY" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="pycharmPY" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="pycharmPC" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="pycharmPC" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="clion" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.clion" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="clion" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="riderRD" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.rider" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="riderRD" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="goland" />
|
||||
<option name="groupId" value="com.jetbrains.intellij.goland" />
|
||||
</LibraryCoordinatesState>
|
||||
<LibraryCoordinatesState>
|
||||
<option name="artifactId" value="goland" />
|
||||
<option name="groupId" value="com.jetbrains" />
|
||||
</LibraryCoordinatesState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -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" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
124
.idea/uiDesigner.xml
generated
Normal file
124
.idea/uiDesigner.xml
generated
Normal file
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
442
.idea/workspace.xml
generated
442
.idea/workspace.xml
generated
@@ -1,13 +1,13 @@
|
||||
<?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="">
|
||||
<list default="true" id="1aabf22b-2f57-46ac-9973-367d8668ffd3" name="Default Changelist" comment="Add file tests">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" 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/Cli.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/de/wulkanat/Cli.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" />
|
||||
</list>
|
||||
<list id="44283a45-f406-407f-bce2-a31bb9bfc0cc" name="Changes by Valentin" comment="Changes by Valentin" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
@@ -31,11 +31,6 @@
|
||||
<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" />
|
||||
@@ -77,6 +72,16 @@
|
||||
<item name="Tasks" type="e4a08cd1:TasksNode" />
|
||||
<item name="verification" type="c8890929:TasksNode$1" />
|
||||
</path>
|
||||
<path>
|
||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
||||
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
|
||||
<item name="Dependencies" type="6de06a37:ExternalSystemViewDefaultContributor$MyDependenciesNode" />
|
||||
</path>
|
||||
<path>
|
||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
||||
<item name="HytaleUpdateBot" type="f1a62948:ProjectNode" />
|
||||
<item name="Run Configurations" type="7b0102dc:RunConfigurationsNode" />
|
||||
</path>
|
||||
</expand>
|
||||
<select />
|
||||
</tree_state>
|
||||
@@ -87,99 +92,225 @@
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Kotlin File" />
|
||||
<option value="Kotlin Class" />
|
||||
<option value="Kotlin Object" />
|
||||
<option value="Kotlin File" />
|
||||
<option value="Enum" />
|
||||
<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="not-sure" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitSEFilterConfiguration">
|
||||
<file-type-list>
|
||||
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||
<filtered-out-file-type name="TAG" />
|
||||
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||
</file-type-list>
|
||||
</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/build.gradle" root0="SKIP_INSPECTION" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/extensions/Embed.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/extensions/File.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/extensions/Jsoup.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/model/JobListingPreview.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/model/BlogPostPreview.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/test/kotlin/org/hmcore/extensions/ColorTest.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/main/kotlin/org/hmcore/extensions/Color.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/src/test/kotlin/org/hmcore/extensions/EmbedTest.kt" root0="FORCE_HIGHLIGHTING" />
|
||||
</component>
|
||||
<component name="MacroExpansionManager">
|
||||
<option name="directoryName" value="o7p0t8es" />
|
||||
</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="Downloaded.Files.Path.Enabled" value="false" />
|
||||
<property name="Repository.Attach.Annotations" value="false" />
|
||||
<property name="Repository.Attach.JavaDocs" value="false" />
|
||||
<property name="Repository.Attach.Sources" value="false" />
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="project.structure.last.edited" value="Modules" />
|
||||
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="aspect.path.notification.shown" value="true" />
|
||||
<property name="codeWithMe.voiceChat.enabledByDefault" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/test" />
|
||||
<property name="node.js.detected.package.eslint" value="true" />
|
||||
<property name="node.js.detected.package.tslint" value="true" />
|
||||
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
||||
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
||||
<property name="project.structure.last.edited" value="Project" />
|
||||
<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" />
|
||||
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||
<recent name="de.wulkanat" />
|
||||
<key name="CreateClassDialog.RecentsKey">
|
||||
<recent name="org.hmcore" />
|
||||
</key>
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<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\src\main\kotlin\de\wulkanat" />
|
||||
<recent name="E:\Git\Orbot\src\main\kotlin\org\hmcore" />
|
||||
<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="org.hmcore.model" />
|
||||
<recent name="de.wulkanat" />
|
||||
</key>
|
||||
<key name="MoveClassesOrPackagesDialog.RECENTS_KEY">
|
||||
<recent name="org" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="JAR Application.HytaleUpdateBot-all-1.0-SNAPSHOT.jar">
|
||||
<configuration name="HytaleUpdateBot [fatJar]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<component name="RunManager" selected="Kotlin.Main">
|
||||
<configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
|
||||
<option name="arquillianRunConfiguration">
|
||||
<value>
|
||||
<option name="containerStateName" value="" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="TEST_OBJECT" value="class" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="BlogPostPreviewTest" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="scriptParameters" value="--tests "org.hmcore.model.BlogPostPreviewTest"" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="fatJar" />
|
||||
<option value=":test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="HytaleUpdateBot [jar]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<configuration name="DiscordWebhookEmbed.Webhook should not throw and return false if supplied invalid URL" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="scriptParameters" value="--tests "org.hmcore.webhook.DiscordWebhookEmbed.Webhook should not throw and return false if supplied invalid URL"" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="jar" />
|
||||
<option value=":test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="HytaleUpdateBot-1.0-SNAPSHOT-all.jar" type="JarApplication" temporary="true">
|
||||
<option name="JAR_PATH" value="$PROJECT_DIR$/build/libs/HytaleUpdateBot-1.0-SNAPSHOT-all.jar" />
|
||||
<configuration name="EnumTest.Enum serialization" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="--tests "org.hmcore.serialization.EnumTest.Enum serialization"" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="HytaleUpdateBot-1.0-SNAPSHOT.jar" type="JarApplication" temporary="true">
|
||||
<option name="JAR_PATH" value="$PROJECT_DIR$/build/libs/HytaleUpdateBot-1.0-SNAPSHOT.jar" />
|
||||
<configuration name="FileTest.Ensure exists should create a file if supplied with null but not write text" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="--tests "org.hmcore.extensions.FileTest.Ensure exists should create a file if supplied with null but not write text"" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="HytaleUpdateBot-all-1.0-SNAPSHOT.jar" type="JarApplication" temporary="true">
|
||||
<option name="JAR_PATH" value="$PROJECT_DIR$/build/libs/HytaleUpdateBot-all-1.0-SNAPSHOT.jar" />
|
||||
<method v="2" />
|
||||
<configuration name="All Tests" type="JUnit" factoryName="JUnit">
|
||||
<module name="HytaleUpdateBot.test" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="directory" />
|
||||
<dir value="$PROJECT_DIR$/src/test" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="Main" type="JetRunConfigurationType" temporary="true" nameIsGenerated="true">
|
||||
<option name="MAIN_CLASS_NAME" value="org.hmcore.Main" />
|
||||
<module name="HytaleUpdateBot.main" />
|
||||
<option name="PROGRAM_PARAMETERS" value="serverDataConvert1" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Gradle.FileTest.Ensure exists should create a file if supplied with null but not write text" />
|
||||
<item itemvalue="Gradle.DiscordWebhookEmbed.Webhook should not throw and return false if supplied invalid URL" />
|
||||
<item itemvalue="Gradle.EnumTest.Enum serialization" />
|
||||
<item itemvalue="Gradle.BlogPostPreviewTest" />
|
||||
<item itemvalue="JUnit.All Tests" />
|
||||
<item itemvalue="Kotlin.Main" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="JAR Application.HytaleUpdateBot-all-1.0-SNAPSHOT.jar" />
|
||||
<item itemvalue="JAR Application.HytaleUpdateBot-1.0-SNAPSHOT-all.jar" />
|
||||
<item itemvalue="Gradle.HytaleUpdateBot [fatJar]" />
|
||||
<item itemvalue="JAR Application.HytaleUpdateBot-1.0-SNAPSHOT.jar" />
|
||||
<item itemvalue="Gradle.HytaleUpdateBot [jar]" />
|
||||
<item itemvalue="Kotlin.Main" />
|
||||
<item itemvalue="Gradle.BlogPostPreviewTest" />
|
||||
<item itemvalue="Gradle.EnumTest.Enum serialization" />
|
||||
<item itemvalue="Gradle.DiscordWebhookEmbed.Webhook should not throw and return false if supplied invalid URL" />
|
||||
<item itemvalue="Gradle.FileTest.Ensure exists should create a file if supplied with null but not write text" />
|
||||
</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>
|
||||
@@ -190,83 +321,162 @@
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1597322033373</updated>
|
||||
<workItem from="1622225780094" duration="9225000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add auto publish feature">
|
||||
<created>1597437833375</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1597437833375</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="Add auto publish feature">
|
||||
<created>1597438052596</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1597438052596</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="[1.1]">
|
||||
<created>1597438317540</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<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="Update Json Serialization">
|
||||
<created>1622221667210</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622221667210</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="Update stuff">
|
||||
<created>1622224992757</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622224992757</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="Add shards count configuration">
|
||||
<created>1622226400158</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622226400158</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="Add Twitter integration Add Job Listening Integration Various Refactorings">
|
||||
<created>1622232573135</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622232573135</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="Changes by Valentin">
|
||||
<created>1622232601071</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622232601071</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00011" summary="Changes by Valentin">
|
||||
<created>1622232613740</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622232613740</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="Add more extensive fake update capabilities">
|
||||
<created>1622233686973</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622233686973</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="Update .gitignore">
|
||||
<created>1622233754088</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622233754088</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00014" summary="Bug fixes">
|
||||
<created>1622234469645</created>
|
||||
<option name="number" value="00014" />
|
||||
<option name="presentableId" value="LOCAL-00014" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622234469645</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00015" summary="Changes by Valentin">
|
||||
<created>1622234477364</created>
|
||||
<option name="number" value="00015" />
|
||||
<option name="presentableId" value="LOCAL-00015" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622234477364</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00016" summary="Refactoring, Tests">
|
||||
<created>1622283383268</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622283383268</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00017" summary="Add more tests">
|
||||
<created>1622284601372</created>
|
||||
<option name="number" value="00017" />
|
||||
<option name="presentableId" value="LOCAL-00017" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1622284601372</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="18" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="WindowStateProjectService">
|
||||
<state x="552" y="179" key="#Project_Structure" timestamp="1597338262424">
|
||||
<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="1597338262424" />
|
||||
<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="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="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>
|
||||
<state x="739" y="173" width="484" height="693" key="#org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog/0.0.1920.1040/1920.-213.2560.1400/-1050.105.1050.1640@0.0.1920.1040" timestamp="1597362199927" />
|
||||
<state x="128" y="270" width="490" height="591" key="#xdebugger.evaluate" timestamp="1597332665464">
|
||||
<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 width="1876" height="161" key="GridCell.Tab.0.bottom" timestamp="1597362407964">
|
||||
<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="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="1597362407964" />
|
||||
<state width="1876" height="161" key="GridCell.Tab.0.center" timestamp="1597362407964">
|
||||
<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="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="1597362407964" />
|
||||
<state width="1876" height="161" key="GridCell.Tab.0.left" timestamp="1597362407964">
|
||||
<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="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="1597362407964" />
|
||||
<state width="1876" height="161" key="GridCell.Tab.0.right" timestamp="1597362407964">
|
||||
<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="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="1597362407964" />
|
||||
<state width="1006" height="588" key="GridCell.Tab.1.bottom" timestamp="1597351329412">
|
||||
<screen x="-1050" y="105" width="1050" height="1640" />
|
||||
</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="1597339868293" />
|
||||
<state width="1006" height="588" key="GridCell.Tab.1.center" timestamp="1597351329412">
|
||||
<screen x="-1050" y="105" width="1050" height="1640" />
|
||||
</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="1597339868293" />
|
||||
<state width="1006" height="588" key="GridCell.Tab.1.left" timestamp="1597351329412">
|
||||
<screen x="-1050" y="105" width="1050" height="1640" />
|
||||
</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="1597339868293" />
|
||||
<state width="1006" height="588" key="GridCell.Tab.1.right" timestamp="1597351329412">
|
||||
<screen x="-1050" y="105" width="1050" height="1640" />
|
||||
</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="1597339868293" />
|
||||
<state x="94" y="257" key="SettingsEditor" timestamp="1597361509050">
|
||||
<screen x="0" y="0" width="1920" height="1040" />
|
||||
</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="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="1597333756907">
|
||||
<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="1597333756907" />
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="oldMeFiltersMigrated" value="true" />
|
||||
</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 add removeInactive command" />
|
||||
<MESSAGE value="prepare twitter integration" />
|
||||
<MESSAGE value="Update Json Serialization" />
|
||||
<MESSAGE value="Update stuff" />
|
||||
<MESSAGE value="Add shards count configuration" />
|
||||
<MESSAGE value="Add Twitter integration Add Job Listening Integration Various Refactorings" />
|
||||
<MESSAGE value="Add more extensive fake update capabilities" />
|
||||
<MESSAGE value="Update .gitignore" />
|
||||
<MESSAGE value="Bug fixes" />
|
||||
<MESSAGE value="Changes by Valentin" />
|
||||
<MESSAGE value="Refactoring, Tests" />
|
||||
<MESSAGE value="Add more tests" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Add more tests" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<watches-manager>
|
||||
@@ -275,4 +485,10 @@
|
||||
</configuration>
|
||||
</watches-manager>
|
||||
</component>
|
||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||
<SUITE FILE_PATH="coverage/HytaleUpdateBot$All_Tests.ic" NAME="All Tests Coverage Results" MODIFIED="1622286395486" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="idea" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" />
|
||||
</component>
|
||||
<component name="se.expertsystem.intellij.aop.aspectj.AspectJProjectComponent">
|
||||
<setting name="isActivated" value="false" />
|
||||
</component>
|
||||
</project>
|
||||
95
README.md
95
README.md
@@ -1,47 +1,72 @@
|
||||
# BlogShot
|
||||
A bot that automatically polls the newest blogpost from [Hytale News Tab](https://www.hytale.com/news) and posts a message into servers if there is a new one.
|
||||
## Setup
|
||||
Okay, this isn't really meant for you to setup, if you want it though it first is easier to just dm me on Twitter [@tale_talk](https://twitter.com/tale_talk) so I can add you to the server list.
|
||||
If you *really* want to set it up yourself, fine.
|
||||
1. first go to the release tab, download the jar, and put it in a folder
|
||||
2. 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}
|
||||
```
|
||||
3. add an empty array to your `servers.json`
|
||||
```json
|
||||
[]
|
||||
```
|
||||
Not sure, but it might be that multiline JSON doesn't work.
|
||||
## Add to your server
|
||||
Click [this](https://discord.com/api/oauth2/authorize?client_id=743447329901641799&permissions=150528&scope=bot) link to invite
|
||||
the bot to your server. Please note that only people with *Administrator* permission will be able to
|
||||
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 | off | [Community|Partner|Verified only] Auto publish the message if in an announcement channel |
|
||||
| %!ping | none | everyone | roleName | What role to ping |
|
||||
| %!setMessage | message | Set a custom message when a blogpost arrives |
|
||||
| %!resetMessage | | Reset the custom message to none |
|
||||
| %!serviceChannel | add | remove | Add/remove channel from service notification list |
|
||||
| %!publishMessage | on | off | [Community|Partner|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 -Xmx1024m -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]`
|
||||
|
||||
I'm not 100% certain how much RAM the bot needs, default is typically `-Xmx256m`, and that lead to some issues, `-Xmx512m` is probably plenty, because my server has
|
||||
tons of unused ram I set it to `-Xmx2048m`, just try and look what works for you.
|
||||
|
||||
## Compiling yourself
|
||||
I developed it under Windows, and had some trouble compiling it on Linux. You mileage may vary.
|
||||
|
||||
## Admin 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
|
||||
```
|
||||
%!addChannel [channelID] [roleID/everyone]
|
||||
```
|
||||
Second argument is optional.
|
||||
### Cause a fake update (test if it works)
|
||||
```
|
||||
%!fakeUpdate
|
||||
```
|
||||
### Stop the server from within Discord
|
||||
```
|
||||
%!stop
|
||||
```
|
||||
### Show servers, channels and roles
|
||||
```
|
||||
%!info
|
||||
```
|
||||
| **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 |
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
||||
## TODO
|
||||
|
||||
Mainly reaction roles for convenience, self setup on invite to server, Twitter integration.
|
||||
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.
|
||||
|
||||
## Other
|
||||
|
||||
Thanks to [Forcellrus](https://github.com/Forcellrus/Discord-Auto-Publisher) for discovering a way to auto publish messages
|
||||
in news channels
|
||||
|
||||
39
build.gradle
39
build.gradle
@@ -1,22 +1,33 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.3.61'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.5.10'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
|
||||
}
|
||||
|
||||
group 'de.wulkanat'
|
||||
version '1.0-SNAPSHOT'
|
||||
group 'org.wulkanat'
|
||||
version '2.0.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://m2.dv8tion.net/releases'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
|
||||
compile 'net.dv8tion:JDA:4.2.0_189'
|
||||
compile 'org.jsoup:jsoup:1.13.1'
|
||||
compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
||||
implementation 'net.dv8tion:JDA:4.2.1_253'
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1'
|
||||
|
||||
implementation 'com.github.redouane59.twitter:twittered:1.20'
|
||||
implementation group: 'org.quartz-scheduler', name: 'quartz', version: '2.3.2'
|
||||
|
||||
testImplementation 'io.mockk:mockk:1.11.0'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.5.10'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
@@ -28,19 +39,7 @@ compileTestKotlin {
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Main-Class': 'de.wulkanat.MainKt')
|
||||
}
|
||||
}
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
baseName = project.name + '-all'
|
||||
from((configurations.compile.findAll { !it.path.endsWith(".pom") }).collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
})
|
||||
with jar
|
||||
manifest {
|
||||
attributes 'Main-Class': 'de.wulkanat.MainKt',
|
||||
'Implementation-Version': version
|
||||
attributes('Main-Class': 'org.hmcore.Main')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Thu Aug 13 18:41:46 CEST 2020
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
53
gradlew
vendored
53
gradlew
vendored
@@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@@ -66,6 +82,7 @@ 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
|
||||
@@ -109,10 +126,11 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
@@ -138,19 +156,19 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -159,14 +177,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
43
gradlew.bat
vendored
43
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@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
|
||||
@@ -13,15 +29,18 @@ 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"
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -35,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -45,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%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
|
||||
|
||||
15
src/main/java/org/hmcore/MessageType.java
Normal file
15
src/main/java/org/hmcore/MessageType.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.hmcore;
|
||||
|
||||
public enum MessageType {
|
||||
|
||||
INVALID (-1),
|
||||
BLOGPOST(0),
|
||||
TWITTER(1),
|
||||
JOB_LISTING(2),
|
||||
WEBSITE_CHANGED(3);
|
||||
|
||||
MessageType(int i) {
|
||||
}
|
||||
|
||||
int i;
|
||||
}
|
||||
40
src/main/java/org/hmcore/TwitterJob.java
Normal file
40
src/main/java/org/hmcore/TwitterJob.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package org.hmcore;
|
||||
|
||||
import com.github.redouane59.twitter.TwitterClient;
|
||||
import com.github.redouane59.twitter.dto.tweet.Tweet;
|
||||
import com.github.redouane59.twitter.signature.TwitterCredentials;
|
||||
import net.dv8tion.jda.api.MessageBuilder;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TwitterJob implements Job {
|
||||
public static TwitterClient twitterClient = new TwitterClient(TwitterCredentials.builder()
|
||||
.accessToken(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getAccessToken())
|
||||
.accessTokenSecret(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getAccessTokenSecret())
|
||||
.apiKey(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getApiKey())
|
||||
.apiSecretKey(Objects.requireNonNull(Admin.adFile.getTwitterApi()).getApiKeySecret())
|
||||
.build());
|
||||
|
||||
public static String hytaleTwitterID = twitterClient.getUserFromUserName("Hytale").getId();
|
||||
|
||||
public static String lastTweetID = twitterClient.getUserTimeline(hytaleTwitterID, 20).get(0).getId();
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) {
|
||||
try {
|
||||
Tweet tweet = twitterClient.getUserTimeline(hytaleTwitterID, 20).get(0);
|
||||
String tweetID = tweet.getId();
|
||||
|
||||
if (!lastTweetID.equalsIgnoreCase(tweetID)) {
|
||||
lastTweetID = tweetID;
|
||||
|
||||
Channels.INSTANCE.sentToAll(new MessageBuilder().append("https://twitter.com/Hytale/status/").append(tweetID).build(), MessageType.TWITTER);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package de.wulkanat
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.JDA
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed
|
||||
import net.dv8tion.jda.api.entities.User
|
||||
import java.awt.Color
|
||||
|
||||
object Admin {
|
||||
val userId: Long
|
||||
val token: String
|
||||
val updateMs: Long
|
||||
|
||||
init {
|
||||
val admin = Json(JsonConfiguration.Stable).parse(AdminFile.serializer(), ADMIN_FILE.readText())
|
||||
userId = admin.adminId
|
||||
token = admin.token
|
||||
updateMs = admin.updateMs
|
||||
}
|
||||
|
||||
var jda: JDA? = null
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
admin = value?.retrieveUserById(userId)?.complete()
|
||||
if (admin == null) {
|
||||
kotlin.io.println("Connection to de.wulkanat.Admin failed!")
|
||||
} else {
|
||||
kotlin.io.println("Connected to ${admin!!.name}. No further errors will be printed here.")
|
||||
}
|
||||
}
|
||||
private var admin: User? = null
|
||||
|
||||
fun println(msg: String) {
|
||||
sendDevMessage(
|
||||
EmbedBuilder()
|
||||
.setTitle(msg)
|
||||
.setColor(Color.WHITE)
|
||||
.build(),
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
fun printlnBlocking(msg: String) {
|
||||
senDevMessageBlocking(
|
||||
EmbedBuilder()
|
||||
.setTitle(msg)
|
||||
.setColor(Color.WHITE)
|
||||
.build(),
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
fun error(msg: String, error: Exception) {
|
||||
sendDevMessage(
|
||||
EmbedBuilder()
|
||||
.setTitle(msg)
|
||||
.setDescription(error.message)
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
, "$msg\n\n${error.message}"
|
||||
)
|
||||
}
|
||||
|
||||
fun errorBlocking(msg: String, error: Exception) {
|
||||
senDevMessageBlocking(
|
||||
EmbedBuilder()
|
||||
.setTitle(msg)
|
||||
.setDescription(error.message)
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
, "$msg\n\n${error.message}"
|
||||
)
|
||||
}
|
||||
|
||||
fun warning(msg: String) {
|
||||
sendDevMessage(
|
||||
EmbedBuilder()
|
||||
.setTitle(msg)
|
||||
.setColor(Color.YELLOW)
|
||||
.build(),
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
fun info() {
|
||||
sendDevMessage(
|
||||
EmbedBuilder()
|
||||
.setTitle("Now watching for new Hytale Blogposts every ${updateMs / 1000}s")
|
||||
.setDescription(Channels.getServerNames().joinToString("\n"))
|
||||
.setColor(Color.GREEN)
|
||||
.build(),
|
||||
"Now watching for new Hytale BlogPosts"
|
||||
)
|
||||
}
|
||||
|
||||
fun silent(msg: String) {
|
||||
kotlin.io.println(msg)
|
||||
}
|
||||
|
||||
private fun senDevMessageBlocking(messageEmbed: MessageEmbed, fallback: String) {
|
||||
val devChannel = admin?.openPrivateChannel() ?: kotlin.run {
|
||||
kotlin.io.println(fallback)
|
||||
return
|
||||
}
|
||||
|
||||
devChannel.queue {
|
||||
it.sendMessage(messageEmbed).complete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendDevMessage(messageEmbed: MessageEmbed, fallback: String) {
|
||||
val devChannel = admin?.openPrivateChannel() ?: kotlin.run {
|
||||
kotlin.io.println(fallback)
|
||||
return
|
||||
}
|
||||
|
||||
devChannel.queue {
|
||||
it.sendMessage(messageEmbed).queue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.wulkanat
|
||||
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
|
||||
class Bot : ListenerAdapter() {
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
val message = event.message
|
||||
|
||||
if (message.contentRaw == "!ping") {
|
||||
val channel = message.channel
|
||||
val time = System.currentTimeMillis()
|
||||
|
||||
channel.sendMessage("Pong!")
|
||||
.queue {
|
||||
it.editMessageFormat("Pong: %d ms", System.currentTimeMillis() - time)
|
||||
.queue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package de.wulkanat
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
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
|
||||
|
||||
object Channels {
|
||||
var jda: JDA? = null
|
||||
val json = Json(JsonConfiguration.Stable)
|
||||
|
||||
/**
|
||||
* List of (ServerID, ChannelID)
|
||||
*/
|
||||
val channels: MutableList<DiscordChannel> =
|
||||
json.parse(DiscordChannel.serializer().list, SERVERS_FILE.readText()).toMutableList()
|
||||
|
||||
fun sentToAll(messageEmbed: MessageEmbed) {
|
||||
if (jda == null)
|
||||
return
|
||||
|
||||
for (channel_pair in channels) {
|
||||
val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
|
||||
|
||||
if (channel_pair.mentionedRole != null) {
|
||||
val message = if (channel_pair.mentionedRole == "everyone") {
|
||||
"New Blogpost @everyone"
|
||||
} else {
|
||||
"New Blogpost <@&${channel_pair.mentionedRole}>"
|
||||
}
|
||||
channel.sendMessage(message).queue()
|
||||
}
|
||||
channel.sendMessage(messageEmbed).queue()
|
||||
}
|
||||
}
|
||||
|
||||
fun checkEveryonePermission() {
|
||||
for (channel_pair in channels) {
|
||||
val channel = jda!!.getTextChannelById(channel_pair.id) ?: continue
|
||||
|
||||
if (channel_pair.mentionedRole == "everyone" &&
|
||||
channel.guild.selfMember.hasPermission(Permission.MESSAGE_MENTION_EVERYONE)
|
||||
) {
|
||||
Admin.warning("Cannot mention everyone on ${channel.guild.name}")
|
||||
} else if (channel.guild.selfMember.hasPermission(Permission.MESSAGE_WRITE)) {
|
||||
Admin.warning("Cannot send any messages on ${channel.guild.name}")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun getServerNames(): List<String> {
|
||||
if (jda == null)
|
||||
return listOf()
|
||||
|
||||
return channels.map {
|
||||
val channel = jda!!.getTextChannelById(it.id)
|
||||
if (channel == null) {
|
||||
Admin.warning("Channel ${it.id} is no longer active!")
|
||||
return@map "**${it.id}** *(inactive)*"
|
||||
}
|
||||
|
||||
val role = if (it.mentionedRole == null) {
|
||||
""
|
||||
} else if (it.mentionedRole == "everyone") {
|
||||
" @everyone"
|
||||
} else {
|
||||
" @${channel.guild.getRoleById(it.mentionedRole)?.name}"
|
||||
}
|
||||
"**${channel.guild.name}**\n#${channel.name}${role}"
|
||||
}
|
||||
}
|
||||
|
||||
fun testServerId(id: Long): TextChannel? {
|
||||
return jda?.getTextChannelById(id)
|
||||
}
|
||||
|
||||
fun addChannel(id: Long, role: String?) {
|
||||
channels.add(DiscordChannel(id, role))
|
||||
saveChannels()
|
||||
}
|
||||
|
||||
private fun saveChannels() {
|
||||
SERVERS_FILE.writeText(
|
||||
json.stringify(
|
||||
DiscordChannel.serializer().list,
|
||||
channels
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
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 kotlin.system.exitProcess
|
||||
|
||||
class Cli : ListenerAdapter() {
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
val msg = event.message.contentRaw
|
||||
if (event.author.idLong != Admin.userId ||
|
||||
!msg.startsWith("%!")
|
||||
) {
|
||||
return
|
||||
}
|
||||
val command = msg.removePrefix("%!").split(" ")
|
||||
|
||||
try {
|
||||
when (command[0]) {
|
||||
"stop" -> exitProcess(1)
|
||||
"fakeUpdate" -> {
|
||||
SiteWatcher.newestBlog = BlogPostPreview(
|
||||
title = "FakePost",
|
||||
imgUrl = "",
|
||||
fullPostUrl = "",
|
||||
author = "wulkanat",
|
||||
date = "now",
|
||||
description = "Lorem Ipsum"
|
||||
)
|
||||
|
||||
Admin.println("Posting on next update cycle.")
|
||||
}
|
||||
"addChannel" -> {
|
||||
val channel = command[1].toLong()
|
||||
var role: String? = null
|
||||
if (command.size == 3) {
|
||||
role = command[2]
|
||||
}
|
||||
val serverChannel = Channels.testServerId(channel)
|
||||
val roleName = serverChannel?.guild?.getRoleById(role ?: "")
|
||||
|
||||
if (serverChannel != null) {
|
||||
if (roleName != null || role == null || role == "everyone") {
|
||||
Channels.addChannel(channel, role)
|
||||
Admin.println("Added server '${serverChannel.name}' for role '${roleName ?: role}'")
|
||||
} else {
|
||||
Admin.warning("Unknown Role ID")
|
||||
}
|
||||
} else {
|
||||
Admin.warning("Unknown Channel ID")
|
||||
}
|
||||
}
|
||||
"info" -> {
|
||||
Admin.info()
|
||||
}
|
||||
}
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package de.wulkanat
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.File
|
||||
|
||||
@Serializable
|
||||
data class DiscordChannel(
|
||||
val id: Long,
|
||||
val mentionedRole: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AdminFile(
|
||||
val adminId: Long,
|
||||
val token: String,
|
||||
val updateMs: Long
|
||||
)
|
||||
|
||||
val SERVERS_FILE = File("servers.json")
|
||||
val ADMIN_FILE = File("admin.json")
|
||||
@@ -1,37 +0,0 @@
|
||||
package de.wulkanat
|
||||
|
||||
import net.dv8tion.jda.api.JDABuilder
|
||||
import net.dv8tion.jda.api.entities.Activity
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent
|
||||
import de.wulkanat.web.SiteWatcher
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
fun main() {
|
||||
// TODO: move toke into file
|
||||
val builder = JDABuilder.createLight(
|
||||
Admin.token,
|
||||
GatewayIntent.GUILD_MESSAGES, GatewayIntent.DIRECT_MESSAGES)
|
||||
.addEventListeners(Bot())
|
||||
.setActivity(Activity.watching("for new Blogposts"))
|
||||
.build()
|
||||
|
||||
builder.addEventListener(Cli())
|
||||
builder.awaitReady()
|
||||
|
||||
Channels.jda = builder
|
||||
Admin.jda = builder
|
||||
Admin.info()
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(object : Thread() {
|
||||
override fun run() {
|
||||
println("Shutting down...")
|
||||
Admin.printlnBlocking("Shutting down")
|
||||
}
|
||||
})
|
||||
|
||||
timer("Updater", daemon = true, initialDelay = 0L, period = Admin.updateMs) {
|
||||
if (SiteWatcher.hasNewBlogPost()) {
|
||||
Channels.sentToAll(SiteWatcher.newestBlog!!.toMessageEmbed())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package de.wulkanat.model
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed
|
||||
import de.wulkanat.extensions.hex2Rgb
|
||||
|
||||
data class BlogPostPreview(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val date: String,
|
||||
val author: String,
|
||||
val imgUrl: String,
|
||||
val fullPostUrl: String
|
||||
) {
|
||||
fun toMessageEmbed(): MessageEmbed {
|
||||
return EmbedBuilder()
|
||||
.setTitle(this.title, this.fullPostUrl)
|
||||
.setDescription(this.description)
|
||||
.setAuthor(this.author)
|
||||
.setThumbnail(this.imgUrl)
|
||||
.setFooter(this.date, "https://www.hytale.com/static/images/logo-h.png")
|
||||
.setColor(hex2Rgb("#337fb0"))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package de.wulkanat.web
|
||||
|
||||
import de.wulkanat.model.BlogPostPreview
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
object BlogPostParser {
|
||||
fun getFistBlog(doc: Document): BlogPostPreview {
|
||||
val posts = doc.getElementsByClass("postWrapper")
|
||||
return parseBlog(posts.first())
|
||||
}
|
||||
|
||||
private fun parseBlog(elm: Element): BlogPostPreview {
|
||||
|
||||
return BlogPostPreview(
|
||||
title = elm.getElementsByClass("post__details__heading").first().text(),
|
||||
imgUrl = elm.getElementsByClass("post__image__frame").first().child(0).attr("src"),
|
||||
fullPostUrl = elm.child(0).absUrl("href"),
|
||||
date = elm.getElementsByClass("post__details__meta__date").first().text(),
|
||||
author = elm.getElementsByClass("post__details__meta__author").first().text(),
|
||||
description = elm.getElementsByClass("post__details__body").first().text()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package de.wulkanat.web
|
||||
|
||||
import de.wulkanat.Admin
|
||||
import de.wulkanat.model.BlogPostPreview
|
||||
import org.jsoup.Jsoup
|
||||
import java.io.IOException
|
||||
|
||||
object SiteWatcher {
|
||||
private const val BLOG_INDEX_URL = "https://www.hytale.com/news"
|
||||
var newestBlog: BlogPostPreview? = null
|
||||
|
||||
fun hasNewBlogPost(): Boolean {
|
||||
Admin.silent("Updating...")
|
||||
|
||||
try {
|
||||
val doc = Jsoup.connect(BLOG_INDEX_URL).get()
|
||||
val newBlog = BlogPostParser.getFistBlog(doc)
|
||||
|
||||
if (newestBlog == newBlog) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (newestBlog == null) {
|
||||
newestBlog = newBlog
|
||||
return false
|
||||
} else {
|
||||
newestBlog = newBlog
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Admin.error("Connection to Hytale Server failed", e)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
104
src/main/kotlin/org/hmcore/Admin.kt
Normal file
104
src/main/kotlin/org/hmcore/Admin.kt
Normal file
@@ -0,0 +1,104 @@
|
||||
@file:JvmName("Admin")
|
||||
|
||||
package org.hmcore
|
||||
|
||||
import org.hmcore.extensions.embed
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed
|
||||
import net.dv8tion.jda.api.entities.User
|
||||
import java.awt.Color
|
||||
|
||||
object Admin {
|
||||
@JvmField
|
||||
val adFile = Json.decodeFromString<AdminFile>(ADMIN_FILE.readText())
|
||||
val userId: Long = adFile.adminId
|
||||
val token: String = adFile.token
|
||||
val updateMs: Long = adFile.updateMs
|
||||
val message: String = adFile.watchingMessage
|
||||
val offlineMessage: String = adFile.offlineMessage
|
||||
|
||||
var admin: User? = null
|
||||
|
||||
fun connectToUser() {
|
||||
Main.jdas.find { jda ->
|
||||
jda.retrieveUserById(userId).complete()?.also { admin = it } != null
|
||||
} ?: return kotlin.io.println("Connection to org.hmcore.Admin failed!")
|
||||
|
||||
kotlin.io.println("Connected to ${admin!!.name}. No further errors will be printed here.")
|
||||
}
|
||||
|
||||
fun println(msg: String) = sendDevMessage(
|
||||
embed {
|
||||
title = msg
|
||||
color = Color.WHITE
|
||||
}, msg
|
||||
)
|
||||
|
||||
fun printlnBlocking(msg: String) = sendDevMessageBlocking(
|
||||
embed {
|
||||
title = msg
|
||||
color = Color.WHITE
|
||||
}, msg
|
||||
)
|
||||
|
||||
fun error(msg: String, error: String, author: User? = null) = sendDevMessage(
|
||||
embed {
|
||||
title = msg
|
||||
description = error
|
||||
color = Color.RED
|
||||
|
||||
author {
|
||||
name = author?.asTag
|
||||
url = author?.avatarUrl
|
||||
icon = author?.avatarUrl
|
||||
}
|
||||
}, "$msg\n\n${error}"
|
||||
)
|
||||
|
||||
fun errorBlocking(msg: String, error: Exception) = sendDevMessageBlocking(
|
||||
embed {
|
||||
title = msg
|
||||
description = error.message
|
||||
color = Color.RED
|
||||
}, "$msg\n\n${error.message}"
|
||||
)
|
||||
|
||||
fun warning(msg: String) = sendDevMessage(
|
||||
embed {
|
||||
title = msg
|
||||
color = Color.YELLOW
|
||||
}, msg
|
||||
)
|
||||
|
||||
fun info() {
|
||||
sendDevMessage(
|
||||
embed {
|
||||
title = "Now watching for new Hytale Blogposts every ${updateMs / 1000}s"
|
||||
description = """
|
||||
${Channels.getServerNames().joinToString("\n")}
|
||||
|
||||
**_Service Channels_**
|
||||
${Channels.getServiceChannelServers().joinToString("\n")}
|
||||
""".trimIndent()
|
||||
color = Color.GREEN
|
||||
|
||||
}, "Now watching for new Hytale BlogPosts"
|
||||
)
|
||||
}
|
||||
|
||||
fun silent(msg: String) {
|
||||
kotlin.io.println(msg)
|
||||
}
|
||||
|
||||
private fun sendDevMessageBlocking(messageEmbed: MessageEmbed, fallback: String) {
|
||||
(admin?.openPrivateChannel() ?: return kotlin.io.println(fallback))
|
||||
.complete().sendMessage(messageEmbed).complete()
|
||||
}
|
||||
|
||||
fun sendDevMessage(messageEmbed: MessageEmbed, fallback: String) {
|
||||
(admin?.openPrivateChannel() ?: return kotlin.io.println(fallback)).queue {
|
||||
it.sendMessage(messageEmbed).queue()
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/main/kotlin/org/hmcore/AdminCli.kt
Normal file
94
src/main/kotlin/org/hmcore/AdminCli.kt
Normal file
@@ -0,0 +1,94 @@
|
||||
package org.hmcore
|
||||
|
||||
import org.hmcore.web.fakeUpdateBlogPost
|
||||
import org.hmcore.web.fakeUpdateJobListings
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
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)
|
||||
) {
|
||||
return
|
||||
}
|
||||
val command = Regex("[^\\s`]+|`[^`]*`").findAll(msg.removePrefix("!")).toList()
|
||||
|
||||
when (command[0].value) {
|
||||
"stop" -> exitProcess(1)
|
||||
"fakeUpdate" -> {
|
||||
if (command.size < 2) {
|
||||
Admin.println("Specify type")
|
||||
} else {
|
||||
val amount = command.getOrNull(2)?.value?.trim('`')?.toIntOrNull() ?: 1
|
||||
when (command[1].value.trim('`')) {
|
||||
"blog" -> fakeUpdateBlogPost(amount)
|
||||
"twitter" -> TwitterJob.lastTweetID = "poggers"
|
||||
"jobs" -> fakeUpdateJobListings(amount)
|
||||
else -> Admin.println("Must be blog|twitter|jobs")
|
||||
}
|
||||
|
||||
Admin.println("Posting $amount on next update cycle.")
|
||||
}
|
||||
}
|
||||
"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()
|
||||
Admin.info()
|
||||
}
|
||||
"removeInactive" -> {
|
||||
Channels.channels.removeAll { channel ->
|
||||
Channels.testServerId(channel.id) ?: run {
|
||||
Admin.println("Removed ${channel.id}")
|
||||
null
|
||||
} == null
|
||||
}
|
||||
Admin.info()
|
||||
Channels.saveChannels()
|
||||
}
|
||||
"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 [blog|twitter|jobs] [amount]**
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
180
src/main/kotlin/org/hmcore/Channels.kt
Normal file
180
src/main/kotlin/org/hmcore/Channels.kt
Normal file
@@ -0,0 +1,180 @@
|
||||
@file:JvmName("Channels")
|
||||
|
||||
package org.hmcore
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import net.dv8tion.jda.api.Permission
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import org.hmcore.extensions.embed
|
||||
import org.hmcore.extensions.toWebhook
|
||||
import java.awt.Color
|
||||
|
||||
object Channels {
|
||||
/**
|
||||
* List of (ServerID, ChannelID)
|
||||
*/
|
||||
var channels: MutableList<DiscordChannel> = refreshChannelsFromDisk()
|
||||
var serviceChannels: MutableList<ServiceChannel> = refreshServiceChannelsFromDisk()
|
||||
|
||||
fun sentToAll(messageEmbed: Message, msgType: MessageType) {
|
||||
try {
|
||||
messageEmbed.toWebhook().send(WEBHOOKS.blogPostsWebhookUrl)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Admin.sendDevMessage(embed {
|
||||
title = "Error"
|
||||
description = e.stackTraceToString()
|
||||
color = Color.red
|
||||
}, e.stackTrace.toString())
|
||||
}
|
||||
|
||||
Main.jdas.forEach { jda ->
|
||||
for (channel_pair in channels) {
|
||||
try {
|
||||
if(!channel_pair.type.equals(msgType)) continue
|
||||
val channel = jda.getTextChannelById(channel_pair.id) ?: continue
|
||||
val customMessage = channel_pair.message?.message ?: ""
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
Main.jdas.forEach {
|
||||
val channel = it.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() {
|
||||
Main.jdas.forEach {
|
||||
for (channel_pair in channels) {
|
||||
val channel = it.getTextChannelById(channel_pair.id) ?: continue
|
||||
|
||||
if (channel_pair.mentionedRole == "everyone" &&
|
||||
channel.guild.selfMember.hasPermission(Permission.MESSAGE_MENTION_EVERYONE)
|
||||
) {
|
||||
Admin.warning("Cannot mention everyone on ${channel.guild.name}")
|
||||
} else if (channel.guild.selfMember.hasPermission(Permission.MESSAGE_WRITE)) {
|
||||
Admin.warning("Cannot send any messages on ${channel.guild.name}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshChannelsFromDisk() =
|
||||
Json.decodeFromString<List<DiscordChannel>>(SERVERS_FILE.readText()).toMutableList()
|
||||
|
||||
fun refreshServiceChannelsFromDisk() =
|
||||
Json.decodeFromString<List<ServiceChannel>>(SERVICE_CHANNELS_FILE.readText()).toMutableList()
|
||||
|
||||
fun getServerNames(server: Long? = null) = Main.jdas.flatMap { jda ->
|
||||
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!")
|
||||
return@map "**${it.id}** *(inactive)*"
|
||||
}
|
||||
|
||||
val role = when (it.mentionedRole) {
|
||||
null -> ""
|
||||
"everyone" -> " @everyone"
|
||||
else -> " @${channel.guild.getRoleById(it.mentionedRole ?: "")?.name}"
|
||||
}
|
||||
val publish = if (it.autoPublish) " (publish)" else ""
|
||||
val type = " " + it.type.toString()
|
||||
|
||||
"**${channel.guild.name}** #${channel.name}${role}${publish}${type}${
|
||||
if (it.message == null) {
|
||||
""
|
||||
} else {
|
||||
"\n*${it.message!!.message}*${if (it.message!!.pushAnnouncement) " (publish)" else ""}"
|
||||
}
|
||||
}"
|
||||
}
|
||||
}
|
||||
|
||||
fun getServiceChannelServers(server: Long? = null): List<String> {
|
||||
|
||||
return Main.jdas.flatMap { jda ->
|
||||
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)"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testServerId(id: Long) =
|
||||
Main.jdas.map { it.getTextChannelById(id) }.firstOrNull()
|
||||
|
||||
fun addChannel(id: Long, msgType: MessageType): DiscordChannel? {
|
||||
if (channels.find { it.id == id && it.type == msgType } != null) {
|
||||
return null
|
||||
}
|
||||
val out = DiscordChannel(id, msgType)
|
||||
channels.add(out)
|
||||
saveChannels()
|
||||
return out
|
||||
}
|
||||
|
||||
fun saveChannels() {
|
||||
SERVERS_FILE.writeText(Json.encodeToString(channels))
|
||||
SERVICE_CHANNELS_FILE.writeText(Json.encodeToString(serviceChannels))
|
||||
}
|
||||
}
|
||||
66
src/main/kotlin/org/hmcore/DataIO.kt
Normal file
66
src/main/kotlin/org/hmcore/DataIO.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
@file:JvmName("DataIO")
|
||||
package org.hmcore
|
||||
|
||||
import org.hmcore.extensions.ensureExists
|
||||
import kotlinx.serialization.Required
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
const val channelVersion = 2.0
|
||||
|
||||
@Serializable
|
||||
data class DiscordChannel(
|
||||
val id: Long,
|
||||
var type: MessageType,
|
||||
var mentionedRole: String? = null,
|
||||
var autoPublish: Boolean = false,
|
||||
var message: CustomMessage? = null,
|
||||
var version: Double? = channelVersion
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ServiceChannel(
|
||||
val id: Long
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CustomMessage(
|
||||
var message: String,
|
||||
var pushAnnouncement: Boolean = false
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AdminFile(
|
||||
@Required val adminId: Long = 12345,
|
||||
@Required val token: String = "12345",
|
||||
@Required val updateMs: Long = 30000,
|
||||
@Required val shards: Int = 6,
|
||||
@Required val watchingMessage: String = "for new Blogposts",
|
||||
@Required val offlineMessage: String = "CONNECTION FAILED",
|
||||
@Required var twitterApi: TwitterApi? = TwitterApi()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TwitterApi(
|
||||
@Required val accessToken: String = "accessTokenHere",
|
||||
@Required val accessTokenSecret: String = "accessTokenSecretHere",
|
||||
@Required val apiKey: String = "apiKeyHere",
|
||||
@Required val apiKeySecret: String = "Api Key secret here"
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Webhooks(
|
||||
@Required val blogPostsWebhookUrl: String = "https://...",
|
||||
@Required val jobListingsWebhookUrl: String = "https://...",
|
||||
)
|
||||
|
||||
val WEBHOOKS_FILE = File("webhooks.json").ensureExists(Json.encodeToString(Webhooks()))
|
||||
val WEBHOOKS = Json.decodeFromString<Webhooks>(WEBHOOKS_FILE.readText())
|
||||
|
||||
val SERVERS_FILE = File("servers.json").ensureExists(Json.encodeToString(listOf<DiscordChannel>()))
|
||||
val SERVICE_CHANNELS_FILE =
|
||||
File("service_channels.json").ensureExists(Json.encodeToString(listOf<ServiceChannel>()))
|
||||
val ADMIN_FILE = File("admin.json").ensureExists(Json.encodeToString(AdminFile()))
|
||||
13
src/main/kotlin/org/hmcore/DiscordRpc.kt
Normal file
13
src/main/kotlin/org/hmcore/DiscordRpc.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.hmcore
|
||||
|
||||
object DiscordRpc {
|
||||
|
||||
fun updatePresence(available: Boolean) {
|
||||
// jda ?: return
|
||||
|
||||
// jda!!.presence.activity = Activity.watching(if (available) Admin.message else Admin.offlineMessage)
|
||||
// jda!!.presence.isIdle = !available
|
||||
// noop
|
||||
if (available) Admin.println("Back online") else Admin.error("Gone offline", "Can't reach Hytale server")
|
||||
}
|
||||
}
|
||||
10
src/main/kotlin/org/hmcore/ErrorHandler.kt
Normal file
10
src/main/kotlin/org/hmcore/ErrorHandler.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package org.hmcore
|
||||
|
||||
import net.dv8tion.jda.api.events.ExceptionEvent
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter
|
||||
|
||||
class ErrorHandler : ListenerAdapter() {
|
||||
override fun onException(event: ExceptionEvent) {
|
||||
Admin.error(event.cause.message ?: event.cause.localizedMessage, event.cause.stackTrace.toString())
|
||||
}
|
||||
}
|
||||
124
src/main/kotlin/org/hmcore/Main.kt
Normal file
124
src/main/kotlin/org/hmcore/Main.kt
Normal file
@@ -0,0 +1,124 @@
|
||||
package org.hmcore
|
||||
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.hmcore.web.getNewBlogPosts
|
||||
import net.dv8tion.jda.api.JDA
|
||||
import net.dv8tion.jda.api.JDABuilder
|
||||
import net.dv8tion.jda.api.MessageBuilder
|
||||
import net.dv8tion.jda.api.entities.Activity
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent
|
||||
import net.dv8tion.jda.api.utils.ChunkingFilter
|
||||
import net.dv8tion.jda.api.utils.MemberCachePolicy
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag
|
||||
import org.hmcore.web.getNewJobListings
|
||||
import org.quartz.CronScheduleBuilder.cronSchedule
|
||||
import org.quartz.JobBuilder.newJob
|
||||
import org.quartz.JobDetail
|
||||
import org.quartz.Trigger
|
||||
import org.quartz.TriggerBuilder.newTrigger
|
||||
import org.quartz.impl.StdSchedulerFactory
|
||||
import java.io.File
|
||||
import javax.security.auth.login.LoginException
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
object Main {
|
||||
@JvmField
|
||||
var jdas = mutableListOf<JDA>()
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
if(args.isEmpty()) startBot() else
|
||||
when(args[0]) {
|
||||
"serverDataConvert1" -> serverDataConvert1()
|
||||
else -> startBot()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun serverDataConvert1() {
|
||||
var file = File("servers.json")
|
||||
if(!file.exists()) return
|
||||
var content = ""
|
||||
file.bufferedReader().readLines().forEach {
|
||||
content += it
|
||||
.replace("""mentionedRole":""".toRegex(),"""type":"BLOGPOST","mentionedRole":""")
|
||||
}
|
||||
file.writeBytes(content.encodeToByteArray())
|
||||
}
|
||||
|
||||
fun startBot() {
|
||||
val builder = JDABuilder.createLight(
|
||||
Admin.token,
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.DIRECT_MESSAGES
|
||||
).setActivity(Activity.watching(Admin.message))
|
||||
|
||||
configureMemoryUsage(builder)
|
||||
|
||||
for (i in 0 until Admin.adFile.shards) {
|
||||
try {
|
||||
jdas.add(builder.useSharding(i, Admin.adFile.shards).build().apply {
|
||||
addEventListener(AdminCli())
|
||||
addEventListener(ErrorHandler())
|
||||
addEventListener(OwnerCli())
|
||||
awaitReady()
|
||||
})
|
||||
} catch (loginException: LoginException) {
|
||||
println("!!! Shard $i could not login !!!")
|
||||
}
|
||||
}
|
||||
|
||||
Admin.connectToUser()
|
||||
Admin.info()
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(object : Thread() {
|
||||
override fun run() {
|
||||
println("Shutting down...")
|
||||
println("Sending shutdown notice to Admin, waiting 5s...")
|
||||
Admin.println("Shutting down")
|
||||
sleep(5000)
|
||||
}
|
||||
})
|
||||
|
||||
timer("UpdaterBlogpost", daemon = true, initialDelay = 0L, period = Admin.updateMs) {
|
||||
getNewBlogPosts()?.forEach {
|
||||
Channels.sentToAll(MessageBuilder().setEmbed(it.toMessageEmbed()).build(), MessageType.BLOGPOST)
|
||||
}
|
||||
}
|
||||
|
||||
timer("UpdaterJob", daemon = true, initialDelay = 0L, period = Admin.updateMs) {
|
||||
getNewJobListings()?.forEach {
|
||||
Channels.sentToAll(MessageBuilder().setEmbed(it.toMessageEmbed()).build(), MessageType.JOB_LISTING)
|
||||
}
|
||||
}
|
||||
|
||||
val scheduler = StdSchedulerFactory.getDefaultScheduler()
|
||||
scheduler.start()
|
||||
|
||||
val job: JobDetail = newJob(TwitterJob::class.java)
|
||||
.withIdentity("job1", "group1")
|
||||
.build()
|
||||
|
||||
// Trigger the job to run now, and then repeat every 5 minutes
|
||||
val trigger: Trigger = newTrigger()
|
||||
.withIdentity("trigger1", "group1")
|
||||
.startNow()
|
||||
.withSchedule(cronSchedule("0 0/5 * 1/1 * ? *"))
|
||||
.build()
|
||||
|
||||
scheduler.scheduleJob(job, trigger)
|
||||
}
|
||||
|
||||
private fun configureMemoryUsage(builder: JDABuilder) {
|
||||
builder.disableCache(CacheFlag.ACTIVITY)
|
||||
builder.setMemberCachePolicy(MemberCachePolicy.VOICE.or(MemberCachePolicy.OWNER))
|
||||
builder.setChunkingFilter(ChunkingFilter.NONE)
|
||||
builder.disableIntents(GatewayIntent.GUILD_PRESENCES, GatewayIntent.GUILD_MESSAGE_TYPING)
|
||||
|
||||
// Consider guilds with more than 50 members as "large".
|
||||
// Large guilds will only provide online members in their setup and thus reduce bandwidth if chunking is disabled.
|
||||
builder.setLargeThreshold(50)
|
||||
}
|
||||
}
|
||||
263
src/main/kotlin/org/hmcore/OwnerCli.kt
Normal file
263
src/main/kotlin/org/hmcore/OwnerCli.kt
Normal file
@@ -0,0 +1,263 @@
|
||||
package org.hmcore
|
||||
|
||||
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 prefix = "%!"
|
||||
|
||||
override fun onMessageReceived(event: MessageReceivedEvent) {
|
||||
val msg = event.message.contentRaw
|
||||
// Only accept admin requests
|
||||
if (event.message.member?.hasPermission(Permission.ADMINISTRATOR) != true || !msg.startsWith(prefix)) {
|
||||
return
|
||||
}
|
||||
|
||||
val command = msg.removePrefix(prefix).split(Regex("\\s+"))
|
||||
val channelId = event.message.channel.idLong
|
||||
val msgType: MessageType
|
||||
|
||||
if(command.size < 2)
|
||||
msgType = MessageType.INVALID
|
||||
else msgType = when (command[1].lowercase()) {
|
||||
"blogpost" -> MessageType.BLOGPOST
|
||||
"twitter" -> MessageType.TWITTER
|
||||
"job" -> MessageType.JOB_LISTING
|
||||
"website" -> MessageType.WEBSITE_CHANGED
|
||||
else -> MessageType.INVALID
|
||||
}
|
||||
|
||||
when (command.first()) {
|
||||
"categories" -> {
|
||||
event.message.channel.sendMessage(EmbedBuilder()
|
||||
.setTitle("Categories")
|
||||
.setColor(Color.YELLOW)
|
||||
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
|
||||
.setDescription("Valid Categories:\n" +
|
||||
" Blogpost\n" +
|
||||
" Twitter\n" +
|
||||
" Job - (changes of Job listings)\n" +
|
||||
" Website - (if the content of some website or subdomain thats owned by hypixel studios gets changed) - soon\n")
|
||||
.build()).queue()
|
||||
return
|
||||
}
|
||||
"add" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
return
|
||||
}
|
||||
val result = Channels.addChannel(channelId, msgType)
|
||||
if (result == null) {
|
||||
event.message.channel.sendMessage("Already added.").queue()
|
||||
} else {
|
||||
event.message.channel.sendMessage("Added.").queue()
|
||||
Admin.info()
|
||||
}
|
||||
}
|
||||
"remove" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.removeAll { it.id == channelId && (it.type == msgType || it.type == MessageType.INVALID) }
|
||||
Channels.saveChannels()
|
||||
if (result) {
|
||||
event.message.channel.sendMessage("Removed.").queue()
|
||||
} else {
|
||||
event.message.channel.sendMessage("This channel is not registered.").queue()
|
||||
}
|
||||
}
|
||||
"publish" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.find { it.id == channelId && it.type == msgType}
|
||||
if (result != null) {
|
||||
if (command.size > 2 && listOf("on", "off").contains(command[2])) {
|
||||
result.autoPublish = command[2] == "on"
|
||||
Channels.saveChannels()
|
||||
|
||||
event.message.channel.sendMessage("Auto publish is now ${command[1]}").queue()
|
||||
} else {
|
||||
event.message.channel.sendMessage("Usage: `${prefix}publish [type] [on|off]`")
|
||||
}
|
||||
} else {
|
||||
event.message.channel.sendMessage("Channel not registered.").queue()
|
||||
}
|
||||
}
|
||||
"ping" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.find { it.id == channelId && it.type == msgType}
|
||||
if (result != null) {
|
||||
if (command.size > 2) {
|
||||
val roles = event.message.guild.getRolesByName(command[2], false)
|
||||
result.mentionedRole = when {
|
||||
command[2] == "everyone" -> {
|
||||
event.message.channel.sendMessage("Now pinging everyone.").queue()
|
||||
"everyone"
|
||||
}
|
||||
command[2] == "none" -> {
|
||||
event.message.channel.sendMessage("Now pinging none.").queue()
|
||||
null
|
||||
}
|
||||
roles.firstOrNull() != null -> {
|
||||
event.message.channel.sendMessage("Now pinging ${roles.first().name}").queue()
|
||||
roles.first().id
|
||||
}
|
||||
else -> {
|
||||
event.message.channel.sendMessage("Unknown role.").queue()
|
||||
result.mentionedRole
|
||||
}
|
||||
}
|
||||
Channels.saveChannels()
|
||||
} else {
|
||||
event.message.channel.sendMessage("Usage: `${prefix}ping [type] [everyone|none|roleName]`")
|
||||
}
|
||||
} else {
|
||||
event.message.channel.sendMessage("Channel is not registered.").queue()
|
||||
}
|
||||
}
|
||||
"setMessage" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.find { it.id == channelId && it.type == msgType }
|
||||
if (result != null) {
|
||||
if (command.size > 2) {
|
||||
val message = command.subList(2, command.size).toString().trim()
|
||||
result.message = CustomMessage(message)
|
||||
Channels.saveChannels()
|
||||
event.message.channel.sendMessage("Set `$message` as message.").queue()
|
||||
} else {
|
||||
event.message.channel.sendMessage("Usage: `${prefix}setMessage [type] [message]`")
|
||||
}
|
||||
} else {
|
||||
event.message.channel.sendMessage("Channel is not registered.").queue()
|
||||
}
|
||||
}
|
||||
"resetMessage" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.find { it.id == channelId && it.type == msgType }
|
||||
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" -> {
|
||||
if (msgType == MessageType.INVALID) {
|
||||
event.message.channel.sendMessage("Please choose a valid category. List valid categories with ${prefix}categories").queue()
|
||||
}
|
||||
val result = Channels.channels.find { it.id == channelId && it.type == msgType }
|
||||
if (result != null) {
|
||||
if (result.message != null) {
|
||||
if (command.size > 2 && listOf("on", "off").contains(command[2])) {
|
||||
result.message?.pushAnnouncement = command[2] == "on"
|
||||
Channels.saveChannels()
|
||||
|
||||
event.message.channel.sendMessage("Auto publish (message) is now ${command[1]}").queue()
|
||||
} else {
|
||||
event.message.channel.sendMessage("Usage: `${prefix}publishMessage [type] [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()
|
||||
}
|
||||
"help" -> {
|
||||
event.message.channel.sendMessage(
|
||||
EmbedBuilder()
|
||||
.setTitle("Help")
|
||||
.setColor(Color.YELLOW)
|
||||
.setAuthor(Admin.admin?.name, Admin.admin?.avatarUrl, Admin.admin?.avatarUrl)
|
||||
.setDescription(
|
||||
"""
|
||||
**${prefix}add [type]**
|
||||
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 [type]**
|
||||
Remove this channel to the notified list
|
||||
**${prefix}publish [type] [on|off]**
|
||||
[Community|Partner|Verified only] Auto publish the message if in an announcement channel
|
||||
**${prefix}ping [type] [none|everyone|roleName]**
|
||||
What role to ping
|
||||
**${prefix}setMessage [type] [message]**
|
||||
Set a custom message to show
|
||||
**${prefix}resetMessag [type]e**
|
||||
Reset the message
|
||||
**${prefix}info**
|
||||
Show an overview about all channels registered on this server
|
||||
**${prefix}categories**
|
||||
Show a list of categories available for alert types
|
||||
**${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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package de.wulkanat.extensions
|
||||
package org.hmcore.extensions
|
||||
|
||||
import java.awt.Color
|
||||
|
||||
@@ -8,4 +8,7 @@ fun hex2Rgb(colorStr: String): Color {
|
||||
Integer.valueOf(colorStr.substring(3, 5), 16),
|
||||
Integer.valueOf(colorStr.substring(5, 7), 16)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Color.toRgb() =
|
||||
(((red shl 8) + green) shl 8) + blue
|
||||
76
src/main/kotlin/org/hmcore/extensions/Embed.kt
Normal file
76
src/main/kotlin/org/hmcore/extensions/Embed.kt
Normal file
@@ -0,0 +1,76 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder
|
||||
import java.awt.Color
|
||||
|
||||
class EmbedBuilderBuilder {
|
||||
val _embed = EmbedBuilder()
|
||||
|
||||
var title: String?
|
||||
set(value) {
|
||||
_embed.setTitle(value)
|
||||
}
|
||||
get() = null
|
||||
var description: String?
|
||||
set(value) {
|
||||
_embed.setDescription(value)
|
||||
}
|
||||
get() = null
|
||||
|
||||
var color: Color
|
||||
set(value) {
|
||||
_embed.setColor(value)
|
||||
}
|
||||
get() = Color.BLACK
|
||||
|
||||
var thumbnail: String?
|
||||
set(value) {
|
||||
_embed.setThumbnail(value)
|
||||
}
|
||||
get() = null
|
||||
|
||||
fun field(builder: FieldBuilderBuilder.() -> Unit) =
|
||||
FieldBuilderBuilder().apply { builder() }.let {
|
||||
_embed.addField(it.name, it.value, it.inline)
|
||||
}
|
||||
|
||||
fun author(builder: AuthorBuilderBuilder.() -> Unit) =
|
||||
AuthorBuilderBuilder().apply { builder() }.let {
|
||||
_embed.setAuthor(it.name, it.url, it.icon)
|
||||
}
|
||||
|
||||
fun title(builder: TitleBuilderBuilder.() -> Unit) =
|
||||
TitleBuilderBuilder().apply { builder() }.let {
|
||||
_embed.setTitle(it.value, it.url)
|
||||
}
|
||||
|
||||
fun footer(builder: FooterBuilderBuilder.() -> Unit) =
|
||||
FooterBuilderBuilder().apply { builder() }.let {
|
||||
_embed.setFooter(it.value, it.iconUrl)
|
||||
}
|
||||
}
|
||||
|
||||
class FieldBuilderBuilder {
|
||||
var name: String? = null
|
||||
var value: String? = null
|
||||
var inline = false
|
||||
}
|
||||
|
||||
class TitleBuilderBuilder {
|
||||
var value: String? = null
|
||||
var url: String? = null
|
||||
}
|
||||
|
||||
class AuthorBuilderBuilder {
|
||||
var name: String? = null
|
||||
var url: String? = null
|
||||
var icon: String? = null
|
||||
}
|
||||
|
||||
class FooterBuilderBuilder {
|
||||
var value: String? = null
|
||||
var iconUrl: String? = null
|
||||
}
|
||||
|
||||
fun embed(builder: EmbedBuilderBuilder.() -> Unit) =
|
||||
EmbedBuilderBuilder().apply { builder() }._embed.build()
|
||||
11
src/main/kotlin/org/hmcore/extensions/File.kt
Normal file
11
src/main/kotlin/org/hmcore/extensions/File.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun File.ensureExists(defaultText: String? = null): File {
|
||||
if (!this.exists()) {
|
||||
this.createNewFile()
|
||||
this.writeText(defaultText ?: return this)
|
||||
}
|
||||
return this
|
||||
}
|
||||
11
src/main/kotlin/org/hmcore/extensions/Jsoup.kt
Normal file
11
src/main/kotlin/org/hmcore/extensions/Jsoup.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
|
||||
operator fun Element.get(className: String): Elements =
|
||||
this.getElementsByClass(className)
|
||||
|
||||
val Elements.text get() = text().trim()
|
||||
val Element.absUrl get(): String = child(0).absUrl("href")
|
||||
val Element.imgSrc get(): String = child(0).attr("src")
|
||||
44
src/main/kotlin/org/hmcore/extensions/Message.kt
Normal file
44
src/main/kotlin/org/hmcore/extensions/Message.kt
Normal file
@@ -0,0 +1,44 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import net.dv8tion.jda.api.entities.Message
|
||||
import org.hmcore.WEBHOOKS
|
||||
import org.hmcore.webhook.*
|
||||
|
||||
fun Message.toWebhook(): DiscordWebhook {
|
||||
val webhook = DiscordWebhook()
|
||||
webhook.content = contentRaw
|
||||
webhook.tts = false
|
||||
|
||||
embeds.forEach { embed ->
|
||||
webhook.embeds.add(DiscordWebhookEmbed().apply {
|
||||
title = embed.title
|
||||
description = embed.description
|
||||
color = embed.color?.toRgb()
|
||||
image = Image(embed.image?.url)
|
||||
url = embed.url
|
||||
|
||||
thumbnail = Thumbnail(embed.thumbnail?.url)
|
||||
author = Author(
|
||||
embed.author?.name,
|
||||
embed.author?.url,
|
||||
embed.author?.iconUrl
|
||||
)
|
||||
footer = Footer(
|
||||
embed.footer?.text,
|
||||
embed.footer?.iconUrl,
|
||||
)
|
||||
|
||||
for (field in embed.fields) {
|
||||
fields.add(
|
||||
Field(
|
||||
field.name,
|
||||
field.value,
|
||||
field.isInline,
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return webhook
|
||||
}
|
||||
33
src/main/kotlin/org/hmcore/model/BlogPostPreview.kt
Normal file
33
src/main/kotlin/org/hmcore/model/BlogPostPreview.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package org.hmcore.model
|
||||
|
||||
import org.hmcore.extensions.hex2Rgb
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.hmcore.extensions.embed
|
||||
|
||||
@Serializable
|
||||
data class BlogPostPreview(
|
||||
val title: String,
|
||||
val description: String,
|
||||
val date: String,
|
||||
val author: String,
|
||||
val imgUrl: String,
|
||||
val fullPostUrl: String
|
||||
) {
|
||||
fun toMessageEmbed() = embed {
|
||||
title {
|
||||
value = this@BlogPostPreview.title
|
||||
url = fullPostUrl
|
||||
}
|
||||
description = this@BlogPostPreview.description
|
||||
color = hex2Rgb("#337FB0")
|
||||
thumbnail = imgUrl
|
||||
|
||||
author {
|
||||
name = author
|
||||
}
|
||||
footer {
|
||||
value = date
|
||||
iconUrl = "https://www.hytale.com/static/images/logo-h.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/main/kotlin/org/hmcore/model/JobListingPreview.kt
Normal file
26
src/main/kotlin/org/hmcore/model/JobListingPreview.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.hmcore.model
|
||||
|
||||
import org.hmcore.extensions.hex2Rgb
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.hmcore.extensions.embed
|
||||
|
||||
@Serializable
|
||||
data class JobListingPreview(
|
||||
val title: String,
|
||||
val department: String,
|
||||
val location: String,
|
||||
val fullListingUrl: String
|
||||
) {
|
||||
fun toMessageEmbed() = embed {
|
||||
title {
|
||||
value = this@JobListingPreview.title
|
||||
url = fullListingUrl
|
||||
}
|
||||
|
||||
description = department
|
||||
color = hex2Rgb("#337fb0")
|
||||
author {
|
||||
name = location
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/main/kotlin/org/hmcore/web/Parser.kt
Normal file
40
src/main/kotlin/org/hmcore/web/Parser.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package org.hmcore.web
|
||||
|
||||
import org.hmcore.extensions.absUrl
|
||||
import org.hmcore.extensions.get
|
||||
import org.hmcore.extensions.imgSrc
|
||||
import org.hmcore.extensions.text
|
||||
import org.hmcore.model.BlogPostPreview
|
||||
import org.hmcore.model.JobListingPreview
|
||||
|
||||
private const val BLOG_POST_STATE_FILE_NAME = "blog_state.json"
|
||||
fun fakeUpdateBlogPost(amount: Int = 1) = removeFromSiteSave<BlogPostPreview>(BLOG_POST_STATE_FILE_NAME, amount)
|
||||
fun getNewBlogPosts() = updateSite("https://hytale.com/news", BLOG_POST_STATE_FILE_NAME) { doc ->
|
||||
doc["postWrapper"].map {
|
||||
BlogPostPreview(
|
||||
title = it["post__details__heading"].text,
|
||||
imgUrl = it["post__image__frame"].first().imgSrc,
|
||||
fullPostUrl = it.absUrl,
|
||||
date = it["post__details__meta__date"].text,
|
||||
author = it["post__details__meta__author"].text,
|
||||
description = it["post__details__body"].text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val JOB_LISTING_STATE_FILE_NAME = "jobs_state.json"
|
||||
fun fakeUpdateJobListings(amount: Int = 1) = removeFromSiteSave<JobListingPreview>(JOB_LISTING_STATE_FILE_NAME, amount)
|
||||
fun getNewJobListings() = updateSite("https://hypixelstudios.com/jobs/", JOB_LISTING_STATE_FILE_NAME) { doc ->
|
||||
doc["current-jobs__departments"].flatMap { jobDepartment ->
|
||||
val jobDepartmentName = jobDepartment["current-jobs__department-name"].text
|
||||
|
||||
jobDepartment["current-jobs__job"].map { job ->
|
||||
JobListingPreview(
|
||||
title = job["current-jobs__job-title"].text,
|
||||
department = jobDepartmentName,
|
||||
location = job["current-jobs__job-location"].text,
|
||||
fullListingUrl = job.absUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/main/kotlin/org/hmcore/web/SiteWatcher.kt
Normal file
42
src/main/kotlin/org/hmcore/web/SiteWatcher.kt
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.hmcore.web
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Removes the first element of a saved JSON list file
|
||||
*/
|
||||
inline fun <reified T> removeFromSiteSave(fileName: String, amount: Int = 1) =
|
||||
File(fileName).takeIf { it.exists() }?.let {
|
||||
it.writeText(
|
||||
if (amount >= 0) Json.encodeToString(
|
||||
Json.decodeFromString<List<T>>(it.readText()).subList(0, amount)
|
||||
)
|
||||
else "[]"
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified T> updateSite(url: String, fileName: String, parser: (Document) -> List<T>) = try {
|
||||
val currentStateFile = File(fileName)
|
||||
|
||||
val retrievedElements = parser(Jsoup.connect(url).get())
|
||||
var currentElements = if (currentStateFile.exists())
|
||||
Json.decodeFromString(currentStateFile.readText()) else retrievedElements
|
||||
|
||||
val newElements = retrievedElements - currentElements
|
||||
currentElements = retrievedElements
|
||||
currentStateFile.writeText(Json.encodeToString(currentElements))
|
||||
|
||||
newElements
|
||||
} catch (e: IOException) {
|
||||
// TODO: put this somewhere else
|
||||
// Admin.error("""Fetching "$url" failed!""", e.message ?: e.localizedMessage)
|
||||
// DiscordRpc.updatePresence(canUpdate.also { canUpdate = false })
|
||||
|
||||
null
|
||||
}
|
||||
92
src/main/kotlin/org/hmcore/webhook/DiscordWebhookEmbed.kt
Normal file
92
src/main/kotlin/org/hmcore/webhook/DiscordWebhookEmbed.kt
Normal file
@@ -0,0 +1,92 @@
|
||||
package org.hmcore.webhook
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
@Serializable
|
||||
data class Footer(
|
||||
val text: String? = null,
|
||||
@SerialName("icon_url")
|
||||
val iconUrl: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Thumbnail(
|
||||
val url: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Image(
|
||||
val url: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Author(
|
||||
val name: String? = null,
|
||||
val url: String? = null,
|
||||
@SerialName("icon_url")
|
||||
val iconUrl: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Field(
|
||||
val name: String? = null,
|
||||
val value: String? = null,
|
||||
val inline: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DiscordWebhookEmbed(
|
||||
var title: String? = null,
|
||||
var description: String? = null,
|
||||
var url: String? = null,
|
||||
var color: Int? = null,
|
||||
|
||||
var footer: Footer? = null,
|
||||
var thumbnail: Thumbnail? = null,
|
||||
var image: Image? = null,
|
||||
var author: Author? = null,
|
||||
var fields: MutableList<Field> = mutableListOf(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DiscordWebhook(
|
||||
var content: String? = null,
|
||||
var username: String? = null,
|
||||
var avatarUrl: String? = null,
|
||||
var tts: Boolean = false,
|
||||
var embeds: MutableList<DiscordWebhookEmbed> = mutableListOf(),
|
||||
) {
|
||||
fun send(url: String): Boolean {
|
||||
var connection: HttpsURLConnection? = null
|
||||
var stream: OutputStream? = null
|
||||
|
||||
return try {
|
||||
connection = URL(url).openConnection() as HttpsURLConnection
|
||||
connection.addRequestProperty("Content-Type", "application/json")
|
||||
connection.addRequestProperty("User-Agent", "Kotlin-DiscordWebhook")
|
||||
connection.doOutput = true
|
||||
connection.requestMethod = "POST"
|
||||
|
||||
stream = connection.outputStream
|
||||
stream.write(Json.encodeToString(this).toByteArray())
|
||||
stream.flush()
|
||||
|
||||
true
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
|
||||
false
|
||||
} finally {
|
||||
stream?.close()
|
||||
connection?.inputStream?.close()
|
||||
connection?.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/test/kotlin/org/hmcore/extensions/ColorTest.kt
Normal file
25
src/test/kotlin/org/hmcore/extensions/ColorTest.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.awt.Color
|
||||
|
||||
class ColorTest {
|
||||
@Test
|
||||
fun `color should parse from hex correctly`() {
|
||||
assertEquals(hex2Rgb("#FFFFFF"), Color.WHITE)
|
||||
assertEquals(hex2Rgb("#000000"), Color.BLACK)
|
||||
assertEquals(hex2Rgb("#FF0000"), Color.RED)
|
||||
assertEquals(hex2Rgb("#00FF00"), Color.GREEN)
|
||||
assertEquals(hex2Rgb("#0000FF"), Color.BLUE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `color should convert to RGB correctly`() {
|
||||
assertEquals(16777215, Color.WHITE.toRgb())
|
||||
assertEquals(0, Color.BLACK.toRgb())
|
||||
assertEquals(16711680, Color.RED.toRgb())
|
||||
assertEquals(65280, Color.GREEN.toRgb())
|
||||
assertEquals(255, Color.BLUE.toRgb())
|
||||
}
|
||||
}
|
||||
90
src/test/kotlin/org/hmcore/extensions/EmbedTest.kt
Normal file
90
src/test/kotlin/org/hmcore/extensions/EmbedTest.kt
Normal file
@@ -0,0 +1,90 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import java.awt.Color
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class EmbedTest {
|
||||
@Test
|
||||
fun `Embed Title DSL should work`() {
|
||||
val dslEmbed = embed {
|
||||
title {
|
||||
value = "Title"
|
||||
url = "https://a.b.c"
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("Title", dslEmbed.title)
|
||||
assertEquals("https://a.b.c", dslEmbed.url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Embed Thumbnail should work`() {
|
||||
val dslEmbed = embed {
|
||||
thumbnail = "https://a.b.c"
|
||||
}
|
||||
|
||||
assertNotNull(dslEmbed.thumbnail)
|
||||
assertEquals("https://a.b.c", dslEmbed.thumbnail!!.url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Embed Footer DSL should work`() {
|
||||
val dslEmbed = embed {
|
||||
footer {
|
||||
value = "Value"
|
||||
iconUrl = "https://a.b.c"
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(dslEmbed.footer)
|
||||
assertEquals("Value", dslEmbed.footer!!.text)
|
||||
assertEquals("https://a.b.c", dslEmbed.footer!!.iconUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Embed DSL should produce same result as native`() {
|
||||
val dslEmbed = embed {
|
||||
title = "A Title"
|
||||
description = "A Description"
|
||||
color = Color.YELLOW
|
||||
|
||||
author {
|
||||
name = "An author"
|
||||
icon = "https://d.e.f"
|
||||
url = "https://a.b.c"
|
||||
}
|
||||
|
||||
field {
|
||||
inline = true
|
||||
name = "Field1"
|
||||
value = "Body1"
|
||||
}
|
||||
|
||||
field {
|
||||
inline = false
|
||||
name = "Field2"
|
||||
value = "Body2"
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("A Title", dslEmbed.title)
|
||||
assertEquals("A Description", dslEmbed.description)
|
||||
assertEquals(Color.YELLOW, dslEmbed.color)
|
||||
|
||||
assertNotNull(dslEmbed.author)
|
||||
assertEquals("An author", dslEmbed.author!!.name)
|
||||
assertEquals("https://d.e.f", dslEmbed.author!!.iconUrl)
|
||||
assertEquals("https://a.b.c", dslEmbed.author!!.url)
|
||||
|
||||
assertEquals(2, dslEmbed.fields.size)
|
||||
assertEquals("Field1", dslEmbed.fields[0].name)
|
||||
assertEquals("Body1", dslEmbed.fields[0].value)
|
||||
assertEquals(true, dslEmbed.fields[0].isInline)
|
||||
|
||||
assertEquals("Field2", dslEmbed.fields[1].name)
|
||||
assertEquals("Body2", dslEmbed.fields[1].value)
|
||||
assertEquals(false, dslEmbed.fields[1].isInline)
|
||||
}
|
||||
}
|
||||
73
src/test/kotlin/org/hmcore/extensions/FileTest.kt
Normal file
73
src/test/kotlin/org/hmcore/extensions/FileTest.kt
Normal file
@@ -0,0 +1,73 @@
|
||||
package org.hmcore.extensions
|
||||
|
||||
import io.mockk.*
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class FileTest {
|
||||
@Test
|
||||
fun `Ensure exists should create a file with default content if it does not exist`() {
|
||||
val file = mockk<File>()
|
||||
mockkStatic("kotlin.io.FilesKt__FileReadWriteKt")
|
||||
|
||||
every { file.exists() } returns false
|
||||
every { file.createNewFile() } returns true
|
||||
every { file.writeText(any()) } just Runs
|
||||
|
||||
file.ensureExists("Default Text")
|
||||
|
||||
verifySequence {
|
||||
file.exists()
|
||||
file.createNewFile()
|
||||
file.writeText("Default Text")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Ensure exists should create a file if supplied with null but not write text`() {
|
||||
val file = mockk<File>()
|
||||
mockkStatic("kotlin.io.FilesKt__FileReadWriteKt")
|
||||
|
||||
every { file.exists() } returns false
|
||||
every { file.createNewFile() } returns true
|
||||
every { file.writeText(any()) } just Runs
|
||||
|
||||
file.ensureExists(null)
|
||||
|
||||
verifySequence {
|
||||
file.exists()
|
||||
file.createNewFile()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Ensure exists should not touch the file if it exists`() {
|
||||
val file = mockk<File>()
|
||||
mockkStatic("kotlin.io.FilesKt__FileReadWriteKt")
|
||||
|
||||
every { file.exists() } returns true
|
||||
every { file.createNewFile() } returns true
|
||||
every { file.writeText(any()) } just Runs
|
||||
|
||||
file.ensureExists(null)
|
||||
|
||||
verifySequence {
|
||||
file.exists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Ensure exists should return itself`() {
|
||||
val file = mockk<File>()
|
||||
mockkStatic("kotlin.io.FilesKt__FileReadWriteKt")
|
||||
|
||||
every { file.exists() } returns true
|
||||
every { file.createNewFile() } returns true
|
||||
every { file.writeText(any()) } just Runs
|
||||
|
||||
val f2 = file.ensureExists(null)
|
||||
|
||||
assertEquals(file, f2)
|
||||
}
|
||||
}
|
||||
34
src/test/kotlin/org/hmcore/model/BlogPostPreviewTest.kt
Normal file
34
src/test/kotlin/org/hmcore/model/BlogPostPreviewTest.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package org.hmcore.model
|
||||
|
||||
import org.hmcore.extensions.hex2Rgb
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class BlogPostPreviewTest {
|
||||
@Test
|
||||
fun `Blog post should be correctly parsed to Embed`() {
|
||||
val embed = BlogPostPreview(
|
||||
title = "Title",
|
||||
description = "Description",
|
||||
date = "01.01.2000",
|
||||
author = "Nobody",
|
||||
imgUrl = "https://a.b.c",
|
||||
fullPostUrl = "https://d.e.f"
|
||||
).toMessageEmbed()
|
||||
|
||||
assertEquals("Title", embed.title)
|
||||
assertEquals("https://d.e.f", embed.url)
|
||||
assertEquals("Description", embed.description)
|
||||
assertEquals(hex2Rgb("#337FB0"), embed.color)
|
||||
assertNotNull(embed.thumbnail)
|
||||
assertEquals("https://a.b.c", embed.thumbnail!!.url)
|
||||
|
||||
assertNotNull(embed.footer)
|
||||
assertEquals("01.01.2000", embed.footer!!.text)
|
||||
assertEquals("https://www.hytale.com/static/images/logo-h.png", embed.footer!!.iconUrl)
|
||||
|
||||
assertNotNull(embed.author)
|
||||
assertEquals(embed.author!!.name, "Nobody")
|
||||
}
|
||||
}
|
||||
26
src/test/kotlin/org/hmcore/model/JobListingPreviewTest.kt
Normal file
26
src/test/kotlin/org/hmcore/model/JobListingPreviewTest.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.hmcore.model
|
||||
|
||||
import org.hmcore.extensions.hex2Rgb
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class JobListingPreviewTest {
|
||||
@Test
|
||||
fun `Job listings should be correctly parsed to Embed`() {
|
||||
val embed = JobListingPreview(
|
||||
title = "Title",
|
||||
department = "Department",
|
||||
location = "Null Island",
|
||||
fullListingUrl = "https://d.e.f"
|
||||
).toMessageEmbed()
|
||||
|
||||
assertEquals("Title", embed.title)
|
||||
assertEquals("https://d.e.f", embed.url)
|
||||
assertEquals("Department", embed.description)
|
||||
assertEquals(hex2Rgb("#337FB0"), embed.color)
|
||||
|
||||
assertNotNull(embed.author)
|
||||
assertEquals(embed.author!!.name, "Null Island")
|
||||
}
|
||||
}
|
||||
17
src/test/kotlin/org/hmcore/serialization/EnumTest.kt
Normal file
17
src/test/kotlin/org/hmcore/serialization/EnumTest.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package org.hmcore.serialization
|
||||
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.hmcore.MessageType
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class EnumTest {
|
||||
|
||||
@Test
|
||||
fun `Enum serialization`() {
|
||||
println(Json.encodeToString(MessageType.BLOGPOST))
|
||||
assertNotNull(MessageType.INVALID)
|
||||
}
|
||||
|
||||
}
|
||||
30
src/test/kotlin/org/hmcore/webhook/DiscordWebhookEmbed.kt
Normal file
30
src/test/kotlin/org/hmcore/webhook/DiscordWebhookEmbed.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package org.hmcore.webhook
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DiscordWebhookEmbed {
|
||||
/*@Test TODO: Test against JSON Schema or something
|
||||
fun `Webhook class should comply with Discord specification`() {
|
||||
val exampleWebhook = """
|
||||
|
||||
""".trimIndent()
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun `Webhook should not throw and return false if supplied invalid URL`() {
|
||||
assertEquals(false, DiscordWebhook().send("not a valid url"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Webhook should return false if connection throws`() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Webhook should send correctly`() {
|
||||
DiscordWebhook(
|
||||
content = "Test"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user