Converted modules to Kotlin.

This commit is contained in:
Erik C. Thauvin 2020-12-02 01:48:19 -08:00
parent fa4457a6f6
commit 9c1ab833da
70 changed files with 3313 additions and 3446 deletions

356
.idea/codeStyles/Project.xml generated Normal file
View file

@ -0,0 +1,356 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="2" />
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value>
<package name="java.awt" withSubpackages="false" static="false" />
</value>
</option>
<option name="JD_INDENT_ON_CONTINUATION" value="true" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_THROWS_LIST" value="true" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="5" />
<option name="METHOD_PARAMETERS_WRAP" value="5" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="ASSERT_STATEMENT_WRAP" value="1" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="1" />
<option name="WHILE_BRACE_FORCE" value="1" />
<option name="FOR_BRACE_FORCE" value="1" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="VARIABLE_ANNOTATION_WRAP" value="2" />
<option name="ENUM_CONSTANTS_WRAP" value="1" />
<indentOptions>
<option name="USE_RELATIVE_INDENTS" value="true" />
</indentOptions>
<arrangement>
<groups>
<group>
<type>OVERRIDDEN_METHODS</type>
<order>BY_NAME</order>
</group>
</groups>
<rules>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
<visibility />
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<FIELD>true</FIELD>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CONSTRUCTOR>true</CONSTRUCTOR>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD>true</METHOD>
<STATIC>true</STATIC>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<METHOD>true</METHOD>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<ENUM>true</ENUM>
</match>
</rule>
</section>
<section>
<rule>
<match>
<INTERFACE>true</INTERFACE>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<CLASS>true</CLASS>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CLASS>true</CLASS>
</match>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_EXTENDS_LIST" value="true" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Erik's Code Style" />
</state>
</component>

2
.idea/mobibot.iml generated
View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="mobibot" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="0.8.0-beta+122" type="JAVA_MODULE" version="4">
<module external.linked.project.id="mobibot" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="0.8.0-beta+147" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="mobibot:main" external.linked.project.path="$MODULE_DIR$/../.." external.root.project.path="$MODULE_DIR$/../.." external.system.id="GRADLE" external.system.module.group="" external.system.module.type="sourceSet" external.system.module.version="0.8.0-beta+122" type="JAVA_MODULE" version="4">
<module external.linked.project.id="mobibot:main" external.linked.project.path="$MODULE_DIR$/../.." external.root.project.path="$MODULE_DIR$/../.." external.system.id="GRADLE" external.system.module.group="" external.system.module.type="sourceSet" external.system.module.version="0.8.0-beta+147" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JVM 11" allPlatforms="JVM [11]" useProjectSettings="false">
@ -8,7 +8,7 @@
</compilerSettings>
<compilerArguments>
<option name="destination" value="$MODULE_DIR$/../../build/classes/kotlin/main" />
<option name="classpath" value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.aksingh/owm-japis/2.5.3.0/c3aca5d34ba937e0c8e9776cec906003b0703044/owm-japis-2.5.3.0.jar:/media/erik/Projects/maven/repository/net/thauvin/erik/pinboard-poster/1.0.1/pinboard-poster-1.0.1.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.4.0/e3765b66f0610afc92053ff1a93a87a544fca2b/kotlin-stdlib-jdk8-1.4.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.3.9/4be434f5e86c1998a273e7f19a7286440894f0b0/kotlinx-coroutines-core-jvm-1.3.9.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.0/9cc187c3dfaf6e4001bdf962e3cdadff7690261b/kotlin-stdlib-jdk7-1.4.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.0/63e75298e93d4ae0b299bb869cf0c627196f8843/kotlin-stdlib-1.4.0.jar:/media/erik/Projects/maven/repository/net/thauvin/erik/semver/1.2.0/semver-1.2.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/cc27715d1c9c8246beb6a33ea099a9ca5d4e5da1/pircbot-1.5.0-sources.jar:/home/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/7a9dd235e6e81db733212202cc4067b5625650cf/pircbot-1.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.github.spotbugs/spotbugs-annotations/4.1.2/9b082a619fb7deeac71684462c2804c76e0df694/spotbugs-annotations-4.1.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome/1.15.0/d3614542b857eccc0555d1ee8dfc36d2043d9c1f/rome-1.15.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/commons-cli/commons-cli/1.4/c51c00206bb913cd8612b24abd9fa98ae89719b1/commons-cli-1.4.jar:/home/erik/.gradle/caches/modules-2/files-2.1/commons-net/commons-net/3.7/11481f2c9feeb0e9b1b828953f2d4ba2f9b2753c/commons-net-3.7.jar:/home/erik/.gradle/caches/modules-2/files-2.1/net.objecthunter/exp4j/0.4.8/cf1cfc0f958077d86ac7452c7e36d944689b2ec4/exp4j-0.4.8.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.11/68e9a6adf7cf8eb7e9d31bbc554c7c75eeaac568/commons-lang3-3.11.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.json/json/20200518/41a767de4bde8f01d53856b905c49b2db8862f13/json-20200518.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jsoup/jsoup/1.13.1/f9577f3732bb7caa4fee8aba5053158f4010c118/jsoup-1.13.1.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.twitter4j/twitter4j-core/4.0.7/5fdb375ccfb3eda7354efb262cbe9b53abccff2/twitter4j-core-4.0.7.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.13.3/4e857439fc4fe974d212adaaaa3b118b8b50e3ec/log4j-core-2.13.3.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.13.3/7cca27a921a18645139cf651c04b83b1a19cfd76/log4j-slf4j-impl-2.13.3.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.13.3/ec1508160b93d274b1add34419b897bae84c6ca9/log4j-api-2.13.3.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.0/1c752cce0ead8d504ccc88a4fed6471fd83ab0dd/kotlin-stdlib-common-1.4.0.jar:/media/erik/Projects/maven/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome-utils/1.15.0/ab1cb95382bf9a8dec81165d328bcbbf1acfb3ae/rome-utils-1.15.0.jar:/media/erik/Projects/maven/repository/org/jdom/jdom2/2.0.6/jdom2-2.0.6.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/converter-gson/2.5.0/1c96fc5d0230f57d36cd09e2541d10829a3352a7/converter-gson-2.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/f645ed69d595b24d4cf8b3fbb64cc505bede8829/gson-2.8.5.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.5.0/713ce36037bf24a76a3974c05cb85c3f754b1cc3/retrofit-2.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.14.2/eaed79ed6bc1e14fad462172b6a09524545b165c/okhttp-3.14.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.17.2/78c7820b205002da4d2d137f6f312bd64b3d6049/okio-1.17.2.jar" />
<option name="classpath" value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/net.aksingh/owm-japis/2.5.3.0/c3aca5d34ba937e0c8e9776cec906003b0703044/owm-japis-2.5.3.0.jar:/media/erik/Projects/maven/repository/net/thauvin/erik/pinboard-poster/1.0.1/pinboard-poster-1.0.1.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.4.20/756521338269950c2a276f1abe6fef8e1a5e5528/kotlin-stdlib-jdk8-1.4.20.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.4.2/4b9c6b2de7cabfb2c9ad7a5c709b1ddb7bbfd2ad/kotlinx-coroutines-core-jvm-1.4.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.4.20/9de2c79e95d4b4699a455e88ba285a95352e0bea/kotlin-stdlib-jdk7-1.4.20.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.20/9be77b243a362b745e365f286627b8724337009c/kotlin-stdlib-1.4.20.jar:/media/erik/Projects/maven/repository/net/thauvin/erik/semver/1.2.0/semver-1.2.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/cc27715d1c9c8246beb6a33ea099a9ca5d4e5da1/pircbot-1.5.0-sources.jar:/home/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/7a9dd235e6e81db733212202cc4067b5625650cf/pircbot-1.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.github.spotbugs/spotbugs-annotations/4.2.0/39d2a464e63fd44bcdbc2332b12a80df274dac83/spotbugs-annotations-4.2.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome/1.15.0/d3614542b857eccc0555d1ee8dfc36d2043d9c1f/rome-1.15.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/commons-cli/commons-cli/1.4/c51c00206bb913cd8612b24abd9fa98ae89719b1/commons-cli-1.4.jar:/home/erik/.gradle/caches/modules-2/files-2.1/commons-net/commons-net/3.7.2/fc22868c06d0b59dc97f23dc93ca77efd9381ab2/commons-net-3.7.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/net.objecthunter/exp4j/0.4.8/cf1cfc0f958077d86ac7452c7e36d944689b2ec4/exp4j-0.4.8.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.11/68e9a6adf7cf8eb7e9d31bbc554c7c75eeaac568/commons-lang3-3.11.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.json/json/20201115/f8e7a9953822c90e0701c3cd50764b5e9063f85c/json-20201115.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jsoup/jsoup/1.13.1/f9577f3732bb7caa4fee8aba5053158f4010c118/jsoup-1.13.1.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.twitter4j/twitter4j-core/4.0.7/5fdb375ccfb3eda7354efb262cbe9b53abccff2/twitter4j-core-4.0.7.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.14.0/e257b0562453f73eabac1bc3181ba33e79d193ed/log4j-core-2.14.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.14.0/d6003a3b3f24fdb476848f4ecabdb2a43354e410/log4j-slf4j-impl-2.14.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.14.0/23cdb2c6babad9b2b0dcf47c6a2c29d504e4c7a8/log4j-api-2.14.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.20/c6761d7805b5312302f2bbd78cda68c976ce0c70/kotlin-stdlib-common-1.4.20.jar:/media/erik/Projects/maven/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome-utils/1.15.0/ab1cb95382bf9a8dec81165d328bcbbf1acfb3ae/rome-utils-1.15.0.jar:/media/erik/Projects/maven/repository/org/jdom/jdom2/2.0.6/jdom2-2.0.6.jar:/home/erik/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/converter-gson/2.5.0/1c96fc5d0230f57d36cd09e2541d10829a3352a7/converter-gson-2.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/f645ed69d595b24d4cf8b3fbb64cc505bede8829/gson-2.8.5.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.5.0/713ce36037bf24a76a3974c05cb85c3f754b1cc3/retrofit-2.5.0.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.14.2/eaed79ed6bc1e14fad462172b6a09524545b165c/okhttp-3.14.2.jar:/home/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.17.2/78c7820b205002da4d2d137f6f312bd64b3d6049/okio-1.17.2.jar" />
<option name="noStdlib" value="true" />
<option name="noReflect" value="true" />
<option name="moduleName" value="mobibot" />
@ -20,11 +20,11 @@
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.0/52ac54a56c9121f54fcca387c5a0f441d1af9be8/kotlin-script-runtime-1.4.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.0/9c5c5503c8f96b477661789168b3f4a3c61a6c6e/kotlin-scripting-common-1.4.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.0/d807bf2ab36765af04b29319989322efd5ad1401/kotlin-scripting-jvm-1.4.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.0/1c752cce0ead8d504ccc88a4fed6471fd83ab0dd/kotlin-stdlib-common-1.4.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.0/63e75298e93d4ae0b299bb869cf0c627196f8843/kotlin-stdlib-1.4.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.20/9793d2f6b262847a2d8127951c5786cf907cc7b1/kotlin-script-runtime-1.4.20.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.20/2e2bf29688a76cec111df56bc5e358c5bbc5057/kotlin-scripting-common-1.4.20.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.20/83704fbbe39946cc2ac6d0f07f41947abeb8dc20/kotlin-scripting-jvm-1.4.20.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.20/c6761d7805b5312302f2bbd78cda68c976ce0c70/kotlin-stdlib-common-1.4.20.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.20/9be77b243a362b745e365f286627b8724337009c/kotlin-stdlib-1.4.20.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.7/8e2eb78158638b33793d204ffef0b65c4a578e1c/kotlinx-coroutines-core-1.3.7.jar" />
<option value="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
</array>
@ -50,35 +50,35 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: net.aksingh:owm-japis:2.5.3.0" level="project" />
<orderEntry type="library" name="Gradle: net.thauvin.erik:pinboard-poster:1.0.1" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.4.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.4.20" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: net.thauvin.erik:semver:1.2.0" level="project" />
<orderEntry type="library" name="Gradle: pircbot:pircbot:1.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: pircbot:pircbot:sources:1.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.github.spotbugs:spotbugs-annotations:4.1.2" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.github.spotbugs:spotbugs-annotations:4.2.0" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome:1.15.0" level="project" />
<orderEntry type="library" name="Gradle: commons-cli:commons-cli:1.4" level="project" />
<orderEntry type="library" name="Gradle: commons-net:commons-net:3.7" level="project" />
<orderEntry type="library" name="Gradle: commons-net:commons-net:3.7.2" level="project" />
<orderEntry type="library" name="Gradle: net.objecthunter:exp4j:0.4.8" level="project" />
<orderEntry type="library" name="Gradle: org.apache.commons:commons-lang3:3.11" level="project" />
<orderEntry type="library" name="Gradle: org.json:json:20200518" level="project" />
<orderEntry type="library" name="Gradle: org.json:json:20201115" level="project" />
<orderEntry type="library" name="Gradle: org.jsoup:jsoup:1.13.1" level="project" />
<orderEntry type="library" name="Gradle: org.twitter4j:twitter4j-core:4.0.7" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-core:2.13.3" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-slf4j-impl:2.13.3" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-api:2.13.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.0" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-core:2.14.0" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-slf4j-impl:2.14.0" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-api:2.14.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.retrofit2:converter-gson:2.5.0" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.gson:gson:2.8.5" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.retrofit2:retrofit:2.5.0" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:3.14.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.3.9" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome-utils:1.15.0" level="project" />
<orderEntry type="library" name="Gradle: org.jdom:jdom2:2.0.6" level="project" />
<orderEntry type="library" name="Gradle: org.slf4j:slf4j-api:1.7.25" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.0" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.20" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:1.17.2" level="project" />
</component>
</module>

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,64 @@
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues>
<ID>ComplexMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongMethod:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongParameterList:Comment.kt$Comment$( bot: Mobibot, cmd: String, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int )</ID>
<ID>LongParameterList:Comment.kt$Comment$( bot: Mobibot, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int )</ID>
<ID>LongParameterList:Comment.kt$Comment$(bot: Mobibot, cmd: String, sender: String, entry: EntryLink, index: Int, commentIndex: Int)</ID>
<ID>LongParameterList:Twitter.kt$Twitter.Companion$( consumerKey: String?, consumerSecret: String?, token: String?, tokenSecret: String?, handle: String?, message: String, isDm: Boolean )</ID>
<ID>MagicNumber:Comment.kt$Comment$3</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter$11</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter$3</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter.Companion$3</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter.Companion$33</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter.Companion$4</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter.Companion$8</ID>
<ID>MagicNumber:Cycle.kt$Cycle$10</ID>
<ID>MagicNumber:Dice.kt$Dice$7</ID>
<ID>MagicNumber:Ignore.kt$Ignore$8</ID>
<ID>MagicNumber:Modules.kt$Modules$7</ID>
<ID>MagicNumber:Recap.kt$Recap.Companion$10</ID>
<ID>MagicNumber:Twitter.kt$Twitter$1000L</ID>
<ID>MagicNumber:Twitter.kt$Twitter$60L</ID>
<ID>MagicNumber:Users.kt$Users$8</ID>
<ID>MagicNumber:Utils.kt$Utils.Companion$30</ID>
<ID>MagicNumber:Utils.kt$Utils.Companion$365</ID>
<ID>MagicNumber:Utils.kt$Utils.Companion$7</ID>
<ID>MagicNumber:View.kt$View$8</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$1.60934</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$32</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$5</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$9</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime$17</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$3</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$3600</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$60</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$86.4</ID>
<ID>MemberNameEqualsClassName:Calc.kt$Calc.Companion$ @JvmStatic fun calc(query: String): String</ID>
<ID>MemberNameEqualsClassName:Lookup.kt$Lookup.Companion$ @JvmStatic @Throws(UnknownHostException::class) fun lookup(query: String): String</ID>
<ID>MemberNameEqualsClassName:WorldTime.kt$WorldTime.Companion$ @JvmStatic fun worldTime(query: String): Message</ID>
<ID>NestedBlockDepth:Addons.kt$Addons$ fun add(command: AbstractCommand, props: Properties)</ID>
<ID>NestedBlockDepth:Comment.kt$Comment$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean )</ID>
<ID>NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$ @JvmStatic fun convertCurrency(query: String): Message</ID>
<ID>NestedBlockDepth:FeedReader.kt$FeedReader$ override fun run()</ID>
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)</ID>
<ID>NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean )</ID>
<ID>NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse( sender: String, cmd: String, args: String, isPrivate: Boolean )</ID>
<ID>NestedBlockDepth:StockQuote.kt$StockQuote$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)</ID>
<ID>NestedBlockDepth:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>NestedBlockDepth:Weather2.kt$Weather2$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)</ID>
<ID>NestedBlockDepth:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>NestedBlockDepth:WorldTime.kt$WorldTime$override fun commandResponse( sender: String, cmd: String, args: String, isPrivate: Boolean )</ID>
<ID>ReturnCount:Utils.kt$Utils.Companion$ @JvmStatic fun colorize(s: String?, color: String): String</ID>
<ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$ @JvmStatic @Throws(ModuleException::class) fun searchGoogle(query: String, apiKey: String?, cseKey: String?): List&lt;Message&gt;</ID>
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID>
<ID>ThrowsCount:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>TooGenericExceptionCaught:FeedReader.kt$FeedReader$e: Exception</ID>
<ID>TooGenericExceptionCaught:StockQuote.kt$StockQuote.Companion$e: NullPointerException</ID>
<ID>TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException</ID>
<ID>TooManyFunctions:Utils.kt$Utils$Companion</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -1,5 +1,5 @@
/*
* Constants.java
* Constants.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,81 +29,81 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot
package net.thauvin.erik.mobibot;
import java.util.Locale;
import java.util.*
/**
* The <code>Constants</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-19
* @since 1.0
* The `Constants` class.
*/
public final class Constants {
object Constants {
/**
* The connect/read timeout in ms.
*/
public static final int CONNECT_TIMEOUT = 5000;
const val CONNECT_TIMEOUT = 5000
/**
* Debug command line argument.
*/
public static final String DEBUG_ARG = "debug";
const val DEBUG_ARG = "debug"
/**
* The debug command.
*/
public static final String DEBUG_CMD = "debug";
const val DEBUG_CMD = "debug"
/**
* Default IRC Port.
*/
public static final int DEFAULT_PORT = 6667;
const val DEFAULT_PORT = 6667
/**
* Default IRC Server.
*/
public static final String DEFAULT_SERVER = "irc.freenode.net";
const val DEFAULT_SERVER = "irc.freenode.net"
/**
* The die command.
*/
public static final String DIE_CMD = "die";
const val DIE_CMD = "die"
/**
* Help command line argument.
*/
public static final String HELP_ARG = "help";
const val HELP_ARG = "help"
/**
* The help command.
*/
public static final String HELP_CMD = "help";
const val HELP_CMD = "help"
/**
* The link command.
*/
public static final String LINK_CMD = "L";
const val LINK_CMD = "L"
/**
* Default locale.
*/
public static final Locale LOCALE = Locale.getDefault();
val LOCALE: Locale = Locale.getDefault()
/**
* The empty title string.
*/
public static final String NO_TITLE = "No Title";
const val NO_TITLE = "No Title"
/**
* Properties command line argument.
*/
public static final String PROPS_ARG = "properties";
const val PROPS_ARG = "properties"
/**
* The timer delay in minutes.
*/
public static final long TIMER_DELAY = 10L;
const val TIMER_DELAY = 10L
/**
* Properties version line argument.
*/
public static final String VERSION_ARG = "version";
/**
* Disables the default constructor.
*/
private Constants() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
const val VERSION_ARG = "version"
}

View file

@ -1,107 +0,0 @@
/*
* FeedReader.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
/**
* Reads a RSS feed.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Feb 1, 2004
* @since 1.0
*/
public class FeedReader implements Runnable {
// Maximum number of feed items to display
private static final int MAX_ITEMS = 5;
// Bot
private final Mobibot bot;
// Nick of the person who sent the message
private final String sender;
// URL to fetch
private final String url;
/**
* Creates a new {@link FeedReader} instance.
*
* @param bot The bot's instance.
* @param sender The nick of the person who sent the message.
* @param url The URL to fetch.
*/
public FeedReader(final Mobibot bot, final String sender, final String url) {
this.bot = bot;
this.sender = sender;
this.url = url;
}
/**
* Fetches the Feed's items.
*/
@Override
public final void run() {
try {
final SyndFeedInput input = new SyndFeedInput();
try (final XmlReader reader = new XmlReader(new URL(url))) {
final SyndFeed feed = input.build(reader);
final List<SyndEntry> items = feed.getEntries();
if (items.isEmpty()) {
bot.send(sender, "There is currently nothing to view.", false);
} else {
SyndEntry item;
for (int i = 0; (i < items.size()) && (i < MAX_ITEMS); i++) {
item = items.get(i);
bot.send(sender, item.getTitle(), false);
bot.send(sender, Utils.helpIndent(Utils.green(item.getLink()), false), false);
}
}
}
} catch (MalformedURLException e) {
bot.getLogger().debug("Invalid feed URL.", e);
bot.send(sender, "The feed URL is invalid.", false);
} catch (Exception e) {
bot.getLogger().debug("Unable to fetch the feed.", e);
bot.send(sender, "An error has occurred while fetching the feed: " + e.getMessage(), false);
}
}
}

View file

@ -0,0 +1,85 @@
/*
* FeedReader.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot
import com.rometools.rome.io.SyndFeedInput
import com.rometools.rome.io.XmlReader
import java.net.MalformedURLException
import java.net.URL
/**
* Reads a RSS feed.
*/
class FeedReader(
// Bot
private val bot: Mobibot,
// Nick of the person who sent the message
private val sender: String,
// URL to fetch
private val url: String
) : Runnable {
/**
* Fetches the Feed's items.
*/
override fun run() {
with(bot) {
try {
val input = SyndFeedInput()
XmlReader(URL(url)).use { reader ->
val feed = input.build(reader)
val items = feed.entries
if (items.isEmpty()) {
send(sender, "There is currently nothing to view.", false)
} else {
var i = 0
while (i < items.size && i < MAX_ITEMS) {
send(sender, items[i].title, false)
send(sender, Utils.helpIndent(Utils.green(items[i].link), false), false)
i++
}
}
}
} catch (e: MalformedURLException) {
logger.debug("Invalid feed URL.", e)
send(sender, "The feed URL is invalid.", false)
} catch (e: Exception) {
logger.debug("Unable to fetch the feed.", e)
send(sender, "An error has occurred while fetching the feed: ${e.message}", false)
}
}
}
companion object {
// Maximum number of feed items to display
private const val MAX_ITEMS = 5
}
}

View file

@ -579,7 +579,7 @@ public class Mobibot extends PircBot {
*/
private boolean helpModules(final String sender, final String topic, final boolean isPrivate) {
for (final AbstractModule module : addons.getModules()) {
for (final String cmd : module.getCommands()) {
for (final String cmd : module.commands) {
if (topic.equals(cmd)) {
module.helpResponse(sender, isPrivate);
return true;
@ -646,7 +646,7 @@ public class Mobibot extends PircBot {
*/
public final void joinChannel() {
joinChannel(ircChannel);
twitter.notification("%1$s %2$s has joined %3$s");
twitter.notification(getName() + " " + ReleaseInfo.VERSION + " has joined " + getChannel());
}
/**
@ -695,7 +695,7 @@ public class Mobibot extends PircBot {
}
// Modules
for (final AbstractModule module : addons.getModules()) { // modules
for (final String c : module.getCommands()) {
for (final String c : module.commands) {
if (cmd.startsWith(c)) {
module.commandResponse(sender, cmd, args, false);
return;
@ -739,7 +739,7 @@ public class Mobibot extends PircBot {
if (cmd.startsWith(Constants.HELP_CMD)) { // help
helpResponse(sender, args, true);
} else if (isOp && "kill".equals(cmd)) { // kill
twitter.notification("%1$s killed by " + sender + " on %3$s");
twitter.notification(getName() + " killed by " + sender + " on " + getChannel());
sendRawLine("QUIT : Poof!");
System.exit(0);
} else if (isOp && Constants.DEBUG_CMD.equals(cmd)) { // debug
@ -753,7 +753,7 @@ public class Mobibot extends PircBot {
send(sender + " has just signed my death sentence.");
TIMER.cancel();
twitter.shutdown();
twitter.notification("%1$s stopped by " + sender + " on %3$s");
twitter.notification(getName() + " stopped by " + sender + " on " + getChannel());
sleep(3);
quitServer("The Bot Is Out There!");
System.exit(0);
@ -766,7 +766,7 @@ public class Mobibot extends PircBot {
}
for (final AbstractModule module : addons.getModules()) {
if (module.isPrivateMsgEnabled()) {
for (final String c : module.getCommands()) {
for (final String c : module.commands) {
if (cmd.equals(c)) {
module.commandResponse(sender, cmd, args, true);
return;

View file

@ -69,8 +69,8 @@ public final class TwitterOAuth {
* @throws TwitterException If an error occurs.
* @throws IOException If an IO error occurs.
*/
@SuppressFBWarnings({"DM_DEFAULT_ENCODING", "IMC_IMMATURE_CLASS_PRINTSTACKTRACE"})
@SuppressWarnings({"PMD.AvoidPrintStackTrace", "PMD.SystemPrintln"})
@SuppressFBWarnings({ "DM_DEFAULT_ENCODING", "IMC_IMMATURE_CLASS_PRINTSTACKTRACE" })
@SuppressWarnings({ "PMD.AvoidPrintStackTrace", "PMD.SystemPrintln" })
public static void main(final String[] args) throws TwitterException, IOException {
if (args.length == 2) {
final twitter4j.Twitter twitter = new TwitterFactory().getInstance();
@ -91,9 +91,10 @@ public final class TwitterOAuth {
}
System.out.println(
"Please add the following to the bot's property file:" + "\n\n" + "twitter-consumerKey="
+ args[0] + '\n' + "twitter-consumerSecret=" + args[1] + '\n' + "twitter-token="
+ accessToken.getToken() + '\n' + "twitter-tokenSecret=" + accessToken.getTokenSecret());
"Please add the following to the bot's property file:" + "\n\n" + "twitter-consumerKey="
+ args[0] + '\n' + "twitter-consumerSecret=" + args[1] + '\n' + "twitter-token="
+ accessToken.getToken() + '\n' + "twitter-tokenSecret=" + accessToken
.getTokenSecret());
} catch (TwitterException te) {
if (401 == te.getStatusCode()) {
System.out.println("Unable to get the access token.");

View file

@ -1,388 +0,0 @@
/*
* Utils.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors;
import org.jsoup.Jsoup;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* Miscellaneous utilities class.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-26
* @since 1.0
*/
public final class Utils {
private static final String[] searchFlags = { "%c", "%n" };
/**
* Disables the default constructor.
*
* @throws UnsupportedOperationException If the constructor is called.
*/
private Utils() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Makes the given int bold.
*
* @param i The int.
* @return The bold string.
*/
public static String bold(final int i) {
return bold(Integer.toString(i));
}
/**
* Makes the given string bold.
*
* @param s The string.
* @return The bold string.
*/
public static String bold(final String s) {
return colorize(s, Colors.BOLD);
}
/**
* Colorize a string.
*
* @param s The string.
* @param color The color.
* @return The colorized string.
*/
static String colorize(final String s, final String color) {
if (s == null) {
return Colors.NORMAL;
} else if (Colors.BOLD.equals(color) || Colors.REVERSE.equals(color)) {
return color + s + color;
}
return color + s + Colors.NORMAL;
}
/**
* Makes the given string cyan.
*
* @param s The string.
* @return The cyan string.
*/
public static String cyan(final String s) {
return colorize(s, Colors.CYAN);
}
/**
* URL encodes the given string.
*
* @param s The string to encode.
* @return The encoded string.
*/
public static String encodeUrl(final String s) {
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}
/**
* Ensures that the given location (File/URL) has a trailing slash (<code>/</code>) to indicate a directory.
*
* @param location The File or URL location.
* @param isUrl Set to true if the location is a URL
* @return The location ending with a slash.
*/
static String ensureDir(final String location, final boolean isUrl) {
if (isUrl) {
if (location.charAt(location.length() - 1) == '/') {
return location;
} else {
return location + '/';
}
} else {
if (location.charAt(location.length() - 1) == File.separatorChar) {
return location;
} else {
return location + File.separatorChar;
}
}
}
/**
* Returns a property as an int.
*
* @param property The property value.
* @param def The default property value.
* @return The port or default value if invalid.
*/
public static int getIntProperty(final String property, final int def) {
int prop;
try {
prop = Integer.parseInt(property);
} catch (NumberFormatException ignore) {
prop = def;
}
return prop;
}
/**
* Makes the given string green.
*
* @param s The string.
* @return The green string.
*/
public static String green(final String s) {
return colorize(s, Colors.DARK_GREEN);
}
/**
* Formats a help command by replacing {@code %c} with the bot's pub/priv command, and {@code %n} with the bot's
* nick.
*
* @param text The help command text.
* @param botNick The bot's nick.
* @param isPrivate The private flag.
* @return The formatted help command.
*/
public static String helpFormat(final String text, final String botNick, final boolean isPrivate) {
final String[] replace = { (isPrivate) ? "/msg " + botNick : botNick + ':', botNick };
return StringUtils.replaceEach(text, searchFlags, replace);
}
/**
* Returns indented help string.
*
* @param help The help string.
* @return The indented help string.
* @see #helpIndent(String, boolean)
*/
public static String helpIndent(final String help) {
return helpIndent(help, true);
}
/**
* Returns indented help string.
*
* @param help The help string.
* @param isBold The bold flag.
* @return The indented help string.
*/
public static String helpIndent(final String help, final boolean isBold) {
return " " + (isBold ? bold(help) : help);
}
/**
* Returns the specified date as an ISO local date string.
*
* @param date The date.
* @return The date in {@link DateTimeFormatter#ISO_LOCAL_DATE} format.
*/
public static String isoLocalDate(final Date date) {
return isoLocalDate(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));
}
/**
* Returns the specified date as an ISO local date string.
*
* @param date The date.
* @return The date in {@link DateTimeFormatter#ISO_LOCAL_DATE} format.
*/
public static String isoLocalDate(final LocalDateTime date) {
return date.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
/**
* Obfuscates the given string.
*
* @param s The string.
* @return The obfuscated string.
*/
public static String obfuscate(final String s) {
if (StringUtils.isNotBlank(s)) {
return StringUtils.repeat('x', s.length());
}
return s;
}
/**
* Returns the plural form of a word, if count &gt; 1.
*
* @param count The count.
* @param word The word.
* @param plural The plural word.
* @return The plural string.
*/
public static String plural(final long count, final String word, final String plural) {
if (count > 1) {
return plural;
} else {
return word;
}
}
/**
* Makes the given string red.
*
* @param s The string.
* @return The red string.
*/
public static String red(final String s) {
return colorize(s, Colors.RED);
}
/**
* Makes the given string reverse color.
*
* @param s The string.
* @return The reverse color string.
*/
public static String reverseColor(final String s) {
return colorize(s, Colors.REVERSE);
}
/**
* Returns today's date.
*
* @return Today's date in {@link DateTimeFormatter#ISO_LOCAL_DATE} format.
*/
public static String today() {
return isoLocalDate(LocalDateTime.now());
}
/**
* Converts XML/XHTML entities to plain text.
*
* @param str The string to unescape.
* @return The unescaped string.
*/
public static String unescapeXml(final String str) {
return Jsoup.parse(str).text();
}
/**
* Converts milliseconds to year month week day hour and minutes.
*
* @param uptime The uptime in milliseconds.
* @return The uptime in year month week day hours and minutes.
*/
public static String uptime(final long uptime) {
final StringBuilder info = new StringBuilder();
long days = TimeUnit.MILLISECONDS.toDays(uptime);
final long years = days / 365;
days %= 365;
final long months = days / 30;
days %= 30;
final long weeks = days / 7;
days %= 7;
final long hours = TimeUnit.MILLISECONDS.toHours(uptime) - TimeUnit.DAYS.toHours(
TimeUnit.MILLISECONDS.toDays(uptime));
final long minutes = TimeUnit.MILLISECONDS.toMinutes(uptime) - TimeUnit.HOURS.toMinutes(
TimeUnit.MILLISECONDS.toHours(uptime));
if (years > 0) {
info.append(years).append(plural(years, " year ", " years "));
}
if (months > 0) {
info.append(weeks).append(plural(months, " month ", " months "));
}
if (weeks > 0) {
info.append(weeks).append(plural(weeks, " week ", " weeks "));
}
if (days > 0) {
info.append(days).append(plural(days, " day ", " days "));
}
if (hours > 0) {
info.append(hours).append(plural(hours, " hour ", " hours "));
}
info.append(minutes).append(plural(minutes, " minute", " minutes"));
return info.toString();
}
/**
* Reads contents of a URL.
*
* @param url The URL to read.
* @return The URL contents.
* @throws IOException If an IO error occurs.
*/
@SuppressFBWarnings("SECSSSRFUC")
public static String urlReader(final URL url) throws IOException {
try (final BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
}
/**
* Returns the specified date formatted as <code>yyyy-MM-dd HH:mm</code>.
*
* @param date The date.
* @return The formatted date.
*/
public static String utcDateTime(final Date date) {
return utcDateTime(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));
}
/**
* Returns the specified date formatted as <code>yyyy-MM-dd HH:mm</code>.
*
* @param date The date.
* @return The formatted date.
*/
public static String utcDateTime(final LocalDateTime date) {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}
}

View file

@ -0,0 +1,301 @@
/*
* Utils.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot
import org.apache.commons.lang3.StringUtils
import org.jibble.pircbot.Colors
import org.jsoup.Jsoup
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors
/**
* Miscellaneous utilities class.
*/
class Utils private constructor() {
companion object {
private val searchFlags = arrayOf("%c", "%n")
/**
* Makes the given int bold.
*/
@JvmStatic
fun bold(i: Int): String {
return bold(i.toString())
}
/**
* Makes the given string bold.
*/
@JvmStatic
fun bold(s: String?): String {
return colorize(s, Colors.BOLD)
}
/**
* Colorize a string.
*/
@JvmStatic
fun colorize(s: String?, color: String): String {
if (s == null) {
return Colors.NORMAL
} else if (Colors.BOLD == color || Colors.REVERSE == color) {
return color + s + color
}
return color + s + Colors.NORMAL
}
/**
* Makes the given string cyan.
*/
@JvmStatic
fun cyan(s: String?): String {
return colorize(s, Colors.CYAN)
}
/**
* URL encodes the given string.
*/
fun encodeUrl(s: String?): String {
return URLEncoder.encode(s, StandardCharsets.UTF_8)
}
/**
* Ensures that the given location (File/URL) has a trailing slash (`/`) to indicate a directory.
*/
@JvmStatic
fun ensureDir(location: String, isUrl: Boolean): String {
return if (location.isNotEmpty()) {
if (isUrl) {
if (location[location.length - 1] == '/') {
location
} else {
"$location/"
}
} else {
if (location[location.length - 1] == File.separatorChar) {
location
} else {
location + File.separatorChar
}
}
} else {
location
}
}
/**
* Returns a property as an int.
*/
@JvmStatic
fun getIntProperty(property: String, def: Int): Int {
return try {
property.toInt()
} catch (ignore: NumberFormatException) {
def
}
}
/**
* Makes the given string green.
*/
@JvmStatic
fun green(s: String?): String {
return colorize(s, Colors.DARK_GREEN)
}
/**
* Formats a help command by replacing `%c` with the bot's pub/priv command, and `%n` with the bot's
* nick.
*/
@JvmStatic
fun helpFormat(text: String?, botNick: String, isPrivate: Boolean): String {
val replace = arrayOf(if (isPrivate) "/msg $botNick" else "$botNick:", botNick)
return StringUtils.replaceEach(text, searchFlags, replace)
}
/**
* Returns indented help string.
*/
@JvmStatic
@JvmOverloads
fun helpIndent(help: String?, isBold: Boolean = true): String {
return " " + if (isBold) bold(help) else help
}
/**
* Returns the specified date as an ISO local date string.
*/
@JvmStatic
fun isoLocalDate(date: Date): String {
return isoLocalDate(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()))
}
/**
* Returns the specified date as an ISO local date string.
*/
@JvmStatic
fun isoLocalDate(date: LocalDateTime): String {
return date.format(DateTimeFormatter.ISO_LOCAL_DATE)
}
/**
* Obfuscates the given string.
*/
@JvmStatic
fun obfuscate(s: String?): String {
return if (s!!.isNotBlank()) {
StringUtils.repeat('x', s.length)
} else s
}
/**
* Returns the plural form of a word, if count &gt; 1.
*/
@JvmStatic
fun plural(count: Long, word: String, plural: String): String {
return if (count > 1) {
plural
} else {
word
}
}
/**
* Makes the given string red.
*/
@JvmStatic
fun red(s: String?): String {
return colorize(s, Colors.RED)
}
/**
* Makes the given string reverse color.
*/
@JvmStatic
fun reverseColor(s: String?): String {
return colorize(s, Colors.REVERSE)
}
/**
* Returns today's date.
*/
@JvmStatic
fun today(): String {
return isoLocalDate(LocalDateTime.now())
}
/**
* Converts XML/XHTML entities to plain text.
*/
@JvmStatic
fun unescapeXml(str: String?): String {
return Jsoup.parse(str).text()
}
/**
* Converts milliseconds to year month week day hour and minutes.
*/
@JvmStatic
fun uptime(uptime: Long): String {
val info = StringBuilder()
var days = TimeUnit.MILLISECONDS.toDays(uptime)
val years = days / 365
days %= 365
val months = days / 30
days %= 30
val weeks = days / 7
days %= 7
val hours = TimeUnit.MILLISECONDS.toHours(uptime) - TimeUnit.DAYS.toHours(
TimeUnit.MILLISECONDS.toDays(uptime)
)
val minutes = TimeUnit.MILLISECONDS.toMinutes(uptime) - TimeUnit.HOURS.toMinutes(
TimeUnit.MILLISECONDS.toHours(uptime)
)
with(info) {
if (years > 0) {
append(years).append(plural(years, " year ", " years "))
}
if (months > 0) {
append(weeks).append(plural(months, " month ", " months "))
}
if (weeks > 0) {
append(weeks).append(plural(weeks, " week ", " weeks "))
}
if (days > 0) {
append(days).append(plural(days, " day ", " days "))
}
if (hours > 0) {
append(hours).append(plural(hours, " hour ", " hours "))
}
append(minutes).append(plural(minutes, " minute", " minutes"))
return toString()
}
}
/**
* Reads contents of a URL.
*/
@JvmStatic
@Throws(IOException::class)
fun urlReader(url: URL): String {
BufferedReader(InputStreamReader(url.openStream(), StandardCharsets.UTF_8))
.use { reader -> return reader.lines().collect(Collectors.joining(System.lineSeparator())) }
}
/**
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
*/
@JvmStatic
fun utcDateTime(date: Date): String {
return utcDateTime(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()))
}
/**
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
*/
@JvmStatic
fun utcDateTime(date: LocalDateTime): String {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
}
}
}

View file

@ -63,9 +63,10 @@ abstract class AbstractCommand(val bot: Mobibot) {
return false
}
open fun getProperty(key: String) : String? {
open fun getProperty(key: String): String? {
return properties[key]
}
open fun getPropertyKeys(): Set<String> {
return properties.keys
}

View file

@ -38,10 +38,7 @@ import net.thauvin.erik.mobibot.Utils
class Cycle(bot: Mobibot) : AbstractCommand(bot) {
private val wait = 10
override val name = "cycle"
override val help = listOf(
"To have the bot leave the channel and come back:",
Utils.helpIndent("%c $name")
)
override val help = listOf("To have the bot leave the channel and come back:", Utils.helpIndent("%c $name"))
override val isOp = true
override val isPublic = false
override val isVisible = true

View file

@ -48,7 +48,7 @@ public class Info extends AbstractCommand {
+ " (" + Utils.green(ReleaseInfo.WEBSITE) + ')',
"Written by " + ReleaseInfo.AUTHOR + " (" + Utils.green(ReleaseInfo.AUTHOR_URL) + ')');
public Info(@NotNull final Mobibot bot) {
public Info(final Mobibot bot) {
super(bot);
}
@ -97,18 +97,14 @@ public class Info extends AbstractCommand {
if (isOp) {
if (getBot().getTell().isEnabled()) {
info.append(", Messages: ")
.append(getBot().getTell().size());
info.append(", Messages: ").append(getBot().getTell().size());
}
if (getBot().getTwitter().isAutoPost()) {
info.append(", Twitter: ")
.append(getBot().getTwitter().entriesCount());
info.append(", Twitter: ").append(getBot().getTwitter().entriesCount());
}
}
info.append(", Recap: ")
.append(Recap.recapCount())
.append(']');
info.append(", Recap: ").append(Recap.recapCount()).append(']');
getBot().send(sender, info.toString(), isPrivate);
}

View file

@ -173,7 +173,8 @@ public class Tell extends AbstractCommand {
Utils.helpIndent("%c " + TELL_CMD + " <nick> <message>"),
"To view queued and sent messages:",
Utils.helpIndent("%c " + TELL_CMD + ' ' + View.VIEW_CMD),
"Messages are kept for " + Utils.bold(maxDays) + Utils.plural(maxDays, " day.", " days."));
"Messages are kept for " + Utils.bold(maxDays)
+ Utils.plural(maxDays, " day.", " days."));
}
@Override
@ -383,8 +384,8 @@ public class Tell extends AbstractCommand {
isPrivate)),
isPrivate);
getBot().send(sender,
"Messages are kept for " + Utils.bold(maxDays) + Utils.plural(maxDays, " day.", " days."),
isPrivate);
"Messages are kept for " + Utils.bold(maxDays)
+ Utils.plural(maxDays, " day.", " days."), isPrivate);
}
}
}

View file

@ -84,7 +84,8 @@ public final class EntriesUtils {
* @param isView Set to true to display the number of comments.
* @return The entry's link.
*/
@SuppressFBWarnings(value = "CE_CLASS_ENVY", justification = "Yes, it does.")
@SuppressFBWarnings(value = "CE_CLASS_ENVY",
justification = "Yes, it does.")
public static String buildLink(final int index, final EntryLink entry, final boolean isView) {
final StringBuilder buff = new StringBuilder().append(Constants.LINK_CMD).append(index + 1)
.append(": ").append('[').append(entry.getNick()).append(']');

View file

@ -1,173 +0,0 @@
/*
* AbstractModule.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* The <code>Module</code> abstract class.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2016-07-01
* @since 1.0
*/
public abstract class AbstractModule {
final Mobibot bot;
final List<String> commands = new ArrayList<>();
final List<String> help = new ArrayList<>();
final Map<String, String> properties = new ConcurrentHashMap<>();
AbstractModule(final Mobibot bot) {
this.bot = bot;
}
/**
* Responds to a command.
*
* @param sender The sender.
* @param cmd The command.
* @param args The command arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/
public abstract void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate);
/**
* Returns the module's commands, if any.
*
* @return The commands.
*/
public List<String> getCommands() {
return commands;
}
/**
* Returns the module's property keys.
*
* @return The keys.
*/
public Set<String> getPropertyKeys() {
return properties.keySet();
}
/**
* Returns <code>true</code> if the module has properties.
*
* @return <code>true</code> or <code>false</code> .
*/
public boolean hasProperties() {
return !properties.isEmpty();
}
/**
* Responds with the module's help.
*
* @param sender The sender.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/
public void helpResponse(final String sender, final boolean isPrivate) {
for (final String h : help) {
bot.send(sender, Utils.helpFormat(h, bot.getNick(), isPrivateMsgEnabled() && isPrivate), isPrivate);
}
}
/**
* Initializes the properties.
*
* @param keys The properties keys to initialize.
*/
public void initProperties(final String... keys) {
for (final String key : keys) {
properties.put(key, "");
}
}
/**
* Returns <code>true</code> if the module is enabled.
*
* @return <code>true</code> or <code>false</code>
*/
public boolean isEnabled() {
if (hasProperties()) {
return isValidProperties();
} else {
return true;
}
}
/**
* Returns <code>true</code> if the module responds to private messages.
*
* @return <code>true</code> or <code>false</code>
*/
public boolean isPrivateMsgEnabled() {
return false;
}
/**
* Ensures that all properties have values.
*
* @return <code>true</code> if the properties are valid, <code>false</code> otherwise.
*/
boolean isValidProperties() {
for (final String s : getPropertyKeys()) {
if (StringUtils.isBlank(properties.get(s))) {
return false;
}
}
return true;
}
/**
* Sets a property key and value.
*
* @param key The key.
* @param value The value.
*/
public void setProperty(final String key, final String value) {
if (StringUtils.isNotBlank(key)) {
properties.put(key, value);
}
}
}

View file

@ -0,0 +1,131 @@
/*
* AbstractModule.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import org.apache.commons.lang3.StringUtils
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* The `Module` abstract class.
*/
abstract class AbstractModule(val bot: Mobibot) {
/**
* The module's commands, if any.
*/
@JvmField
val commands: MutableList<String> = ArrayList()
@JvmField
val help: MutableList<String> = ArrayList()
val properties: MutableMap<String, String> = ConcurrentHashMap()
/**
* Responds to a command.
*/
abstract fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
)
/**
* Returns the module's property keys.
*/
val propertyKeys: Set<String>
get() = properties.keys
/**
* Returns `true` if the module has properties.
*/
fun hasProperties(): Boolean {
return properties.isNotEmpty()
}
/**
* Responds with the module's help.
*/
open fun helpResponse(sender: String, isPrivate: Boolean) {
for (h in help) {
bot.send(sender, Utils.helpFormat(h, bot.nick, isPrivateMsgEnabled && isPrivate), isPrivate)
}
}
/**
* Initializes the properties.
*/
fun initProperties(vararg keys: String) {
for (key in keys) {
properties[key] = ""
}
}
/**
* Returns `true` if the module is enabled.
*/
val isEnabled: Boolean
get() = if (hasProperties()) {
isValidProperties
} else {
true
}
/**
* Returns `true` if the module responds to private messages.
*/
open val isPrivateMsgEnabled: Boolean = false
/**
* Ensures that all properties have values.
*/
open val isValidProperties: Boolean
get() {
for (s in propertyKeys) {
if (StringUtils.isBlank(properties[s])) {
return false
}
}
return true
}
/**
* Sets a property key and value.
*/
fun setProperty(key: String, value: String) {
if (StringUtils.isNotBlank(key)) {
properties[key] = value
}
}
}

View file

@ -1,99 +0,0 @@
/*
* Calc.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
import java.text.DecimalFormat;
/**
* The Calc module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2016-07-01
* @since 1.0
*/
public class Calc extends AbstractModule {
// Calc command
private static final String CALC_CMD = "calc";
/**
* The default constructor.
*/
public Calc(final Mobibot bot) {
super(bot);
commands.add(CALC_CMD);
help.add("To solve a mathematical calculation:");
help.add(Utils.helpIndent("%c " + CALC_CMD + " <calculation>"));
}
/**
* Performs a calculation.
*
* <p>1 + 1 * 2</p>
*
* @param query The query.
* @return The calculation result.
*/
static String calc(final String query) {
final DecimalFormat decimalFormat = new DecimalFormat("#.##");
try {
final Expression calc = new ExpressionBuilder(query).build();
return query.replace(" ", "") + " = " + Utils.bold(decimalFormat.format(calc.evaluate()));
} catch (Exception e) {
return "No idea. This is the kind of math I don't get.";
}
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (StringUtils.isNotBlank(args)) {
bot.send(calc(args));
} else {
helpResponse(sender, isPrivate);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* AbstractModuleTest.java
* Calc.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,41 +29,53 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static org.assertj.core.api.Assertions.assertThat;
import net.objecthunter.exp4j.ExpressionBuilder
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import org.apache.commons.lang3.StringUtils
import java.text.DecimalFormat
/**
* The <code>AbstractModuleTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
* The Calc module.
*/
final class AbstractModuleTest {
private AbstractModuleTest() {
throw new UnsupportedOperationException("Illegal constructor call.");
class Calc(bot: Mobibot) : AbstractModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
if (StringUtils.isNotBlank(args)) {
bot.send(calc(args))
} else {
helpResponse(sender, isPrivate)
}
}
@SuppressFBWarnings("CE_CLASS_ENVY")
static void testAbstractModule(final AbstractModule module) {
final String name = module.getClass().getName();
companion object {
// Calc command
private const val CALC_CMD = "calc"
assertThat(module.isEnabled()).as(name + ": enabled").isNotEqualTo(module.hasProperties());
assertThat(module.getCommands().size()).as(name + ": commands > 0").isGreaterThan(0);
if (!module.hasProperties()) {
assertThat(module.getPropertyKeys().size()).as(name + ": no properties").isEqualTo(0);
module.setProperty("test", "test");
module.setProperty("", "invalid");
/**
* Performs a calculation. e.g.: 1 + 1 * 2
*/
@JvmStatic
fun calc(query: String): String {
val decimalFormat = DecimalFormat("#.##")
return try {
val calc = ExpressionBuilder(query).build()
query.replace(" ", "") + " = " + Utils.bold(decimalFormat.format(calc.evaluate()))
} catch (e: IllegalArgumentException) {
"No idea. This is the kind of math I don't get."
}
}
}
assertThat(module.getPropertyKeys().size()).as(name + ": properties > 0").isGreaterThan(0);
module.setProperty("invalid", "");
assertThat(module.isValidProperties()).as(name + ": invalid properties").isFalse();
init {
commands.add(CALC_CMD)
help.add("To solve a mathematical calculation:")
help.add(Utils.helpIndent("%c $CALC_CMD <calculation>"))
}
}

View file

@ -1,253 +0,0 @@
/*
* CurrencyConverter.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Constants;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import javax.xml.XMLConstants;
import java.io.IOException;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.apache.commons.lang3.StringUtils.upperCase;
/**
* The CurrentConverter module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Feb 11, 2004
* @since 1.0
*/
@SuppressWarnings("PMD.UseConcurrentHashMap")
public final class CurrencyConverter extends ThreadedModule {
// Currency command
private static final String CURRENCY_CMD = "currency";
// Rates keyword
private static final String CURRENCY_RATES_KEYWORD = "rates";
// Empty rate table.
private static final String EMPTY_RATE_TABLE = "Sorry, but the exchange rate table is empty.";
// Exchange rates
private static final Map<String, String> EXCHANGE_RATES = new TreeMap<>();
// Exchange rates table URL
private static final String EXCHANGE_TABLE_URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
// Last exchange rates table publication date
private static String pubDate = "";
/**
* Creates a new {@link CurrencyConverter} instance.
*/
public CurrencyConverter(final Mobibot bot) {
super(bot);
commands.add(CURRENCY_CMD);
}
/**
* Converts from a currency to another.
*
* <p>100 USD to EUR</p>
*
* @param query The query.
* @return The {@link Message} contained the converted currency.
*/
static Message convertCurrency(final String query) {
final String[] cmds = query.split(" ");
if (cmds.length == 4) {
if (cmds[3].equals(cmds[1]) || "0".equals(cmds[0])) {
return new PublicMessage("You're kidding, right?");
} else {
final String to = upperCase(cmds[1]);
final String from = upperCase(cmds[3]);
if (EXCHANGE_RATES.containsKey(to) && EXCHANGE_RATES.containsKey(from)) {
try {
final double amt = Double.parseDouble(cmds[0].replace(",", ""));
final double doubleFrom = Double.parseDouble(EXCHANGE_RATES.get(to));
final double doubleTo = Double.parseDouble(EXCHANGE_RATES.get(from));
return new PublicMessage(
NumberFormat.getCurrencyInstance(Constants.LOCALE).format(amt).substring(1)
+ ' '
+ upperCase(cmds[1])
+ " = "
+ NumberFormat.getCurrencyInstance(Constants.LOCALE)
.format((amt * doubleTo) / doubleFrom)
.substring(1)
+ ' '
+ upperCase(cmds[3]));
} catch (NumberFormatException e) {
return new ErrorMessage("Let's try with some real numbers next time, okay?");
}
} else {
return new ErrorMessage("Sounds like monopoly money to me!");
}
}
}
return new ErrorMessage("Invalid query. Let's try again.");
}
static List<String> currencyRates() {
final List<String> rates = new ArrayList<>(33);
for (final Map.Entry<String, String> rate : EXCHANGE_RATES.entrySet()) {
rates.add(" " + rate.getKey() + ": " + StringUtils.leftPad(rate.getValue(), 8));
}
return rates;
}
static void loadRates() throws ModuleException {
if (EXCHANGE_RATES.isEmpty()) {
try {
final SAXBuilder builder = new SAXBuilder();
// See https://rules.sonarsourcecom/java/tag/owasp/RSPEC-2755
builder.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
builder.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
builder.setIgnoringElementContentWhitespace(true);
final Document doc = builder.build(new URL(EXCHANGE_TABLE_URL));
final Element root = doc.getRootElement();
final Namespace ns = root.getNamespace("");
final Element cubeRoot = root.getChild("Cube", ns);
final Element cubeTime = cubeRoot.getChild("Cube", ns);
pubDate = cubeTime.getAttribute("time").getValue();
final List<Element> cubes = cubeTime.getChildren();
for (final Element cube : cubes) {
EXCHANGE_RATES.put(
cube.getAttribute("currency").getValue(),
cube.getAttribute("rate").getValue());
}
EXCHANGE_RATES.put("EUR", "1");
} catch (JDOMException | IOException e) {
throw new ModuleException(e.getMessage(),
"An error has occurred while parsing the exchange rates table.",
e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
synchronized (this) {
if (!pubDate.equals(Utils.today())) {
EXCHANGE_RATES.clear();
}
}
super.commandResponse(sender, cmd, args, isPrivate);
}
/**
* Converts the specified currencies.
*/
@SuppressFBWarnings("REDOS")
@Override
void run(final String sender, final String cmd, final String query, final boolean isPrivate) {
if (EXCHANGE_RATES.isEmpty()) {
try {
loadRates();
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
}
}
if (EXCHANGE_RATES.isEmpty()) {
bot.send(sender, EMPTY_RATE_TABLE, true);
} else if (query.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+")) {
final Message msg = convertCurrency(query);
bot.send(sender, msg);
if (msg.isError()) {
helpResponse(sender, isPrivate);
}
} else if (query.contains(CURRENCY_RATES_KEYWORD)) {
bot.send(sender, "The currency rates for " + Utils.bold(pubDate) + " are:", isPrivate);
bot.sendList(sender, currencyRates(), 3, isPrivate, false);
} else {
helpResponse(sender, isPrivate);
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final String sender, final boolean isPrivate) {
if (EXCHANGE_RATES.isEmpty()) {
try {
loadRates();
} catch (ModuleException e) {
bot.getLogger().debug(e.getDebugMessage(), e);
}
}
if (EXCHANGE_RATES.isEmpty()) {
bot.send(sender, EMPTY_RATE_TABLE, isPrivate);
} else {
bot.send(sender, "To convert from one currency to another:", isPrivate);
bot.send(sender,
Utils.helpIndent(Utils.helpFormat("%c " + CURRENCY_CMD + " 100 USD to EUR",
bot.getNick(), isPrivateMsgEnabled())), isPrivate);
bot.send(sender, "For a listing of current rates:", isPrivate);
bot.send(sender,
Utils.helpIndent(Utils.helpFormat("%c " + CURRENCY_CMD + ' ' + CURRENCY_RATES_KEYWORD,
bot.getNick(), isPrivateMsgEnabled())), isPrivate);
bot.send(sender, "The supported currencies are: ", isPrivate);
bot.sendList(sender, new ArrayList<>(EXCHANGE_RATES.keySet()), 11, isPrivate, false);
}
}
}

View file

@ -0,0 +1,234 @@
/*
* CurrencyConverter.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.PublicMessage
import org.apache.commons.lang3.StringUtils
import org.jdom2.JDOMException
import org.jdom2.input.SAXBuilder
import java.io.IOException
import java.net.URL
import java.text.NumberFormat
import java.util.*
import javax.xml.XMLConstants
/**
* The CurrentConverter module.
*/
class CurrencyConverter(bot: Mobibot) : ThreadedModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
synchronized(this) {
if (pubDate != Utils.today()) {
EXCHANGE_RATES.clear()
}
}
super.commandResponse(sender, cmd, args, isPrivate)
}
/**
* Converts the specified currencies.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
if (EXCHANGE_RATES.isEmpty()) {
try {
loadRates()
} catch (e: ModuleException) {
logger.warn(e.debugMessage, e)
}
}
if (EXCHANGE_RATES.isEmpty()) {
send(sender, EMPTY_RATE_TABLE, true)
} else if (args.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+".toRegex())) {
val msg = convertCurrency(args)
send(sender, msg)
if (msg.isError) {
helpResponse(sender, isPrivate)
}
} else if (args.contains(CURRENCY_RATES_KEYWORD)) {
send(sender, "The currency rates for ${Utils.bold(pubDate)} are:", isPrivate)
sendList(sender, currencyRates(), 3, isPrivate, false)
} else {
helpResponse(sender, isPrivate)
}
}
}
override fun helpResponse(sender: String, isPrivate: Boolean) {
with(bot) {
if (EXCHANGE_RATES.isEmpty()) {
try {
loadRates()
} catch (e: ModuleException) {
logger.debug(e.debugMessage, e)
}
}
if (EXCHANGE_RATES.isEmpty()) {
send(sender, EMPTY_RATE_TABLE, isPrivate)
} else {
send(sender, "To convert from one currency to another:", isPrivate)
send(
sender,
Utils.helpIndent(
Utils.helpFormat("%c $CURRENCY_CMD 100 USD to EUR", nick, isPrivateMsgEnabled)
),
isPrivate
)
send(sender, "For a listing of current rates:", isPrivate)
send(
sender,
Utils.helpIndent(
Utils.helpFormat("%c $CURRENCY_CMD $CURRENCY_RATES_KEYWORD", nick, isPrivateMsgEnabled)
),
isPrivate
)
send(sender, "The supported currencies are: ", isPrivate)
sendList(sender, ArrayList(EXCHANGE_RATES.keys), 11, isPrivate, false)
}
}
}
companion object {
// Currency command
private const val CURRENCY_CMD = "currency"
// Rates keyword
private const val CURRENCY_RATES_KEYWORD = "rates"
// Empty rate table.
private const val EMPTY_RATE_TABLE = "Sorry, but the exchange rate table is empty."
// Exchange rates
private val EXCHANGE_RATES: MutableMap<String, String> = TreeMap()
// Exchange rates table URL
private const val EXCHANGE_TABLE_URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"
// Last exchange rates table publication date
private var pubDate = ""
/**
* Converts from a currency to another.
*/
@JvmStatic
fun convertCurrency(query: String): Message {
val cmds = query.split(" ").toTypedArray()
return if (cmds.size == 4) {
if (cmds[3] == cmds[1] || "0" == cmds[0]) {
PublicMessage("You're kidding, right?")
} else {
val to = StringUtils.upperCase(cmds[1])
val from = StringUtils.upperCase(cmds[3])
if (EXCHANGE_RATES.containsKey(to) && EXCHANGE_RATES.containsKey(from)) {
try {
val amt = cmds[0].replace(",", "").toDouble()
val doubleFrom = EXCHANGE_RATES[to]!!.toDouble()
val doubleTo = EXCHANGE_RATES[from]!!.toDouble()
PublicMessage(
NumberFormat.getCurrencyInstance(Constants.LOCALE).format(amt).substring(1)
+ " ${StringUtils.upperCase(cmds[1])} = "
+ NumberFormat.getCurrencyInstance(Constants.LOCALE)
.format(amt * doubleTo / doubleFrom).substring(1)
+ " ${StringUtils.upperCase(cmds[3])}"
)
} catch (e: NumberFormatException) {
ErrorMessage("Let's try with some real numbers next time, okay?")
}
} else {
ErrorMessage("Sounds like monopoly money to me!")
}
}
} else ErrorMessage("Invalid query. Let's try again.")
}
@JvmStatic
fun currencyRates(): List<String> {
val rates = ArrayList<String>(33)
for ((key, value) in EXCHANGE_RATES) {
rates.add(" $key: ${StringUtils.leftPad(value, 8)}")
}
return rates
}
@JvmStatic
@Throws(ModuleException::class)
fun loadRates() {
if (EXCHANGE_RATES.isEmpty()) {
try {
val builder = SAXBuilder()
// See https://rules.sonarsourcecom/java/tag/owasp/RSPEC-2755
builder.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "")
builder.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "")
builder.ignoringElementContentWhitespace = true
val doc = builder.build(URL(EXCHANGE_TABLE_URL))
val root = doc.rootElement
val ns = root.getNamespace("")
val cubeRoot = root.getChild("Cube", ns)
val cubeTime = cubeRoot.getChild("Cube", ns)
pubDate = cubeTime.getAttribute("time").value
val cubes = cubeTime.children
for (cube in cubes) {
EXCHANGE_RATES[cube.getAttribute("currency").value] = cube.getAttribute("rate").value
}
EXCHANGE_RATES["EUR"] = "1"
} catch (e: JDOMException) {
throw ModuleException(
e.message,
"An JDOM parsing error has occurred while parsing the exchange rates table.",
e
)
} catch (e: IOException) {
throw ModuleException(
e.message,
"An IO error has occurred while parsing the exchange rates table.",
e
)
}
}
}
}
init {
commands.add(CURRENCY_CMD)
}
}

View file

@ -1,98 +0,0 @@
/*
* Dice.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import java.security.SecureRandom;
import static net.thauvin.erik.mobibot.Utils.bold;
/**
* The Dice module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-28
* @since 1.0
*/
public final class Dice extends AbstractModule {
// Dice command
private static final String DICE_CMD = "dice";
/**
* The default constructor.
*/
public Dice(final Mobibot bot) {
super(bot);
commands.add(DICE_CMD);
help.add("To roll the dice:");
help.add(Utils.helpIndent("%c " + DICE_CMD));
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom();
int i = r.nextInt(6) + 1;
int y = r.nextInt(6) + 1;
final int playerTotal = i + y;
bot.send(bot.getChannel(),
sender + " rolled two dice: " + bold(i) + " and " + bold(y) + " for a total of "
+ bold(playerTotal), isPrivate);
i = r.nextInt(6) + 1;
y = r.nextInt(6) + 1;
final int total = i + y;
bot.action(
"rolled two dice: " + bold(i) + " and " + bold(y) + " for a total of " + bold(total));
if (playerTotal < total) {
bot.action("wins.");
} else if (playerTotal > total) {
bot.action("lost.");
} else {
bot.action("tied.");
}
}
}

View file

@ -0,0 +1,103 @@
/*
* Dice.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import kotlin.random.Random
/**
* The Dice module.
*/
class Dice(bot: Mobibot) : AbstractModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
val roll = roll()
val playerRoll = roll()
val total = roll.first + roll.second
val playerTotal = playerRoll.first + playerRoll.second
with(bot) {
send(
channel,
"$sender rolled two dice: ${Utils.bold(playerRoll.first)} and ${Utils.bold(playerRoll.second)}"
+ " for a total of ${Utils.bold(playerTotal)}",
isPrivate
)
action(
"rolled two dice: ${Utils.bold(roll.first)} and ${Utils.bold(roll.second)}" +
" for a total of ${Utils.bold(total)}"
)
when (winLoseOrTie(total, playerTotal)) {
Result.WIN -> action("wins.")
Result.LOSE -> action("lost.")
else -> action("tied.")
}
}
}
enum class Result {
WIN, LOSE, TIE
}
private fun roll(): Pair<Int, Int> {
return Pair(Random.nextInt(1, 7), Random.nextInt(1, 7))
}
companion object {
// Dice command
private const val DICE_CMD = "dice"
fun winLoseOrTie(bot: Int, player: Int): Result {
return when {
bot > player -> {
Result.WIN
}
bot < player -> {
Result.LOSE
}
else -> {
Result.TIE
}
}
}
}
init {
commands.add(DICE_CMD)
help.add("To roll the dice:")
help.add(Utils.helpIndent("%c $DICE_CMD"))
}
}

View file

@ -1,147 +0,0 @@
/*
* GoogleSearch.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.NoticeMessage;
import org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* The GoogleSearch module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Feb 7, 2004
* @since 1.0
*/
public final class GoogleSearch extends ThreadedModule {
// Google API Key property
static final String GOOGLE_API_KEY_PROP = "google-api-key";
// Google Custom Search Engine ID property
static final String GOOGLE_CSE_KEY_PROP = "google-cse-cx";
// Google command
private static final String GOOGLE_CMD = "google";
/**
* Creates a new {@link GoogleSearch} instance.
*/
public GoogleSearch(final Mobibot bot) {
super(bot);
commands.add(GOOGLE_CMD);
help.add("To search Google:");
help.add(Utils.helpIndent("%c " + GOOGLE_CMD + " <query>"));
initProperties(GOOGLE_API_KEY_PROP, GOOGLE_CSE_KEY_PROP);
}
/**
* Performs a search on Google.
*
* @param query The search query.
* @param apiKey The Google API key.
* @param cseKey The Google CSE key.
* @return The {@link Message} array containing the search results.
* @throws ModuleException If an error occurs while searching.
*/
@SuppressFBWarnings({ "URLCONNECTION_SSRF_FD", "REC_CATCH_EXCEPTION" })
@SuppressWarnings(("PMD.AvoidInstantiatingObjectsInLoops"))
static List<Message> searchGoogle(final String query, final String apiKey, final String cseKey)
throws ModuleException {
if (StringUtils.isBlank(apiKey) || StringUtils.isBlank(cseKey)) {
throw new ModuleException(StringUtils.capitalize(GOOGLE_CMD) + " is disabled. The API keys are missing.");
}
if (StringUtils.isNotBlank(query)) {
final ArrayList<Message> results = new ArrayList<>();
try {
final URL url =
new URL("https://www.googleapis.com/customsearch/v1?key="
+ apiKey
+ "&cx="
+ cseKey
+ "&q="
+ Utils.encodeUrl(query)
+ "&filter=1&num=5&alt=json");
final JSONObject json = new JSONObject(Utils.urlReader(url));
final JSONArray ja = json.getJSONArray("items");
for (int i = 0; i < ja.length(); i++) {
final JSONObject j = ja.getJSONObject(i);
results.add(new NoticeMessage(Utils.unescapeXml(j.getString("title"))));
results.add(
new NoticeMessage(Utils.helpIndent(j.getString("link"), false), Colors.DARK_GREEN));
}
} catch (IOException e) {
throw new ModuleException("searchGoogle(" + query + ')', "An error has occurred searching Google.", e);
}
return results;
} else {
throw new ModuleException("Invalid query. Please try again.");
}
}
/**
* Searches Google.
*/
@Override
void run(final String sender, final String cmd, final String query, final boolean isPrivate) {
if (StringUtils.isNotBlank(query)) {
try {
final List<Message> results = searchGoogle(query, properties.get(GOOGLE_API_KEY_PROP),
properties.get(GOOGLE_CSE_KEY_PROP));
for (final Message msg : results) {
bot.send(sender, msg);
}
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage(), isPrivate);
}
} else {
helpResponse(sender, isPrivate);
}
}
}

View file

@ -0,0 +1,125 @@
/*
* GoogleSearch.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import org.apache.commons.lang3.StringUtils
import org.jibble.pircbot.Colors
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.URL
import java.util.*
/**
* The GoogleSearch module.
*/
class GoogleSearch(bot: Mobibot) : ThreadedModule(bot) {
/**
* Searches Google.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
if (StringUtils.isNotBlank(args)) {
try {
val results = searchGoogle(
args, properties[GOOGLE_API_KEY_PROP],
properties[GOOGLE_CSE_KEY_PROP]
)
for (msg in results) {
send(sender, msg)
}
} catch (e: ModuleException) {
logger.warn(e.debugMessage, e)
send(sender, e.message, isPrivate)
}
} else {
helpResponse(sender, isPrivate)
}
}
}
companion object {
// Google API Key property
const val GOOGLE_API_KEY_PROP = "google-api-key"
// Google Custom Search Engine ID property
const val GOOGLE_CSE_KEY_PROP = "google-cse-cx"
// Google command
private const val GOOGLE_CMD = "google"
/**
* Performs a search on Google.
*/
@JvmStatic
@Throws(ModuleException::class)
fun searchGoogle(query: String, apiKey: String?, cseKey: String?): List<Message> {
if (StringUtils.isBlank(apiKey) || StringUtils.isBlank(cseKey)) {
throw ModuleException("${StringUtils.capitalize(GOOGLE_CMD)} is disabled. The API keys are missing.")
}
return if (StringUtils.isNotBlank(query)) {
val results = ArrayList<Message>()
try {
val url = URL(
"https://www.googleapis.com/customsearch/v1?key=$apiKey&cx=$cseKey" +
"&q=${Utils.encodeUrl(query)}&filter=1&num=5&alt=json"
)
val json = JSONObject(Utils.urlReader(url))
val ja = json.getJSONArray("items")
for (i in 0 until ja.length()) {
val j = ja.getJSONObject(i)
results.add(NoticeMessage(Utils.unescapeXml(j.getString("title"))))
results.add(NoticeMessage(Utils.helpIndent(j.getString("link"), false), Colors.DARK_GREEN))
}
} catch (e: IOException) {
throw ModuleException("searchGoogle($query)", "An IO error has occurred searching Google.", e)
} catch (e: JSONException) {
throw ModuleException("searchGoogle($query)", "A JSON error has occurred searching Google.", e)
}
results
} else {
throw ModuleException("Invalid query. Please try again.")
}
}
}
init {
commands.add(GOOGLE_CMD)
help.add("To search Google:")
help.add(Utils.helpIndent("%c $GOOGLE_CMD <query>"))
initProperties(GOOGLE_API_KEY_PROP, GOOGLE_CSE_KEY_PROP)
}
}

View file

@ -1,110 +0,0 @@
/*
* Joke.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import org.json.JSONObject;
import java.net.URL;
/**
* The Joke module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-20
* @since 1.0
*/
public final class Joke extends ThreadedModule {
// Joke command
private static final String JOKE_CMD = "joke";
// ICNDB URL
private static final String JOKE_URL =
"http://api.icndb.com/jokes/random?escape=javascript&exclude=[explicit]&limitTo=[nerdy]";
/**
* Creates a new {@link Joke} instance.
*/
public Joke(final Mobibot bot) {
super(bot);
commands.add(JOKE_CMD);
help.add("To retrieve a random joke:");
help.add(Utils.helpIndent("%c " + JOKE_CMD));
}
/**
* Retrieves a random joke.
*
* @return The {@link Message} containing the new joke.
* @throws ModuleException If an error occurs while retrieving a new joke.
*/
static Message randomJoke() throws ModuleException {
try {
final URL url = new URL(JOKE_URL);
final JSONObject json = new JSONObject(Utils.urlReader(url));
return new PublicMessage(
json.getJSONObject("value").get("joke").toString().replace("\\'", "'")
.replace("\\\"", "\""));
} catch (Exception e) {
throw new ModuleException("randomJoke()", "An error has occurred retrieving a random joke.", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
new Thread(() -> run(sender, cmd, args, isPrivate)).start();
}
/**
* Returns a random joke from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a>.
*/
@Override
void run(final String sender, final String cmd, final String arg, final boolean isPrivate) {
try {
bot.send(Utils.cyan(randomJoke().getMsg()));
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage(), isPrivate);
}
}
}

View file

@ -0,0 +1,100 @@
/*
* Joke.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.PublicMessage
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.URL
/**
* The Joke module.
*/
class Joke(bot: Mobibot) : ThreadedModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
Thread { run(sender, cmd, args, isPrivate) }.start()
}
/**
* Returns a random joke from [The Internet Chuck Norris Database](http://www.icndb.com/).
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) = try {
bot.send(Utils.cyan(randomJoke().msg))
} catch (e: ModuleException) {
bot.logger.warn(e.debugMessage, e)
bot.send(sender, e.message, isPrivate)
}
companion object {
// Joke command
private const val JOKE_CMD = "joke"
// ICNDB URL
private const val JOKE_URL =
"http://api.icndb.com/jokes/random?escape=javascript&exclude=[explicit]&limitTo=[nerdy]"
/**
* Retrieves a random joke.
*/
@JvmStatic
@Throws(ModuleException::class)
fun randomJoke(): Message {
return try {
val url = URL(JOKE_URL)
val json = JSONObject(Utils.urlReader(url))
PublicMessage(
json.getJSONObject("value")["joke"].toString().replace("\\'", "'")
.replace("\\\"", "\"")
)
} catch (e: IOException) {
throw ModuleException("randomJoke()", "An IO error has occurred retrieving a random joke.", e)
} catch (e: JSONException) {
throw ModuleException("randomJoke()", "An JSON error has occurred retrieving a random joke.", e)
}
}
}
init {
commands.add(JOKE_CMD)
help.add("To retrieve a random joke:")
help.add(Utils.helpIndent("%c $JOKE_CMD"))
}
}

View file

@ -1,193 +0,0 @@
/*
* Lookup.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Constants;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.net.whois.WhoisClient;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* The Lookup module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-26
* @since 1.0
*/
public final class Lookup extends AbstractModule {
/**
* The whois default host.
*/
static final String WHOIS_HOST = "whois.arin.net";
// Lookup command
private static final String LOOKUP_CMD = "lookup";
/**
* The default constructor.
*/
public Lookup(final Mobibot bot) {
super(bot);
commands.add(LOOKUP_CMD);
help.add("To perform a DNS lookup query:");
help.add(Utils.helpIndent("%c " + LOOKUP_CMD + " <ip address or hostname>"));
}
/**
* Performs a DNS lookup on the specified query.
*
* @param query The IP address or hostname.
* @return The lookup query result string.
* @throws java.net.UnknownHostException If the host is unknown.
*/
public static String lookup(final String query)
throws UnknownHostException {
final StringBuilder buffer = new StringBuilder();
final InetAddress[] results = InetAddress.getAllByName(query);
String hostInfo;
for (final InetAddress result : results) {
if (result.getHostAddress().equals(query)) {
hostInfo = result.getHostName();
if (hostInfo.equals(query)) {
throw new UnknownHostException();
}
} else {
hostInfo = result.getHostAddress();
}
if (buffer.length() > 0) {
buffer.append(", ");
}
buffer.append(hostInfo);
}
return buffer.toString();
}
/**
* Performs a whois IP query.
*
* @param query The IP address.
* @return The IP whois data, if any.
* @throws java.io.IOException If a connection error occurs.
*/
private static String[] whois(final String query)
throws IOException {
return whois(query, WHOIS_HOST);
}
/**
* Performs a whois IP query.
*
* @param query The IP address.
* @param host The whois host.
* @return The IP whois data, if any.
* @throws java.io.IOException If a connection error occurs.
*/
public static String[] whois(final String query, final String host)
throws IOException {
final WhoisClient whoisClient = new WhoisClient();
final String[] lines;
try {
whoisClient.setDefaultTimeout(Constants.CONNECT_TIMEOUT);
whoisClient.connect(host);
whoisClient.setSoTimeout(Constants.CONNECT_TIMEOUT);
whoisClient.setSoLinger(false, 0);
if (WHOIS_HOST.equals(host)) {
lines = whoisClient.query("n - " + query).split("\n");
} else {
lines = whoisClient.query(query).split("\n");
}
} finally {
whoisClient.disconnect();
}
return lines;
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (args.matches("(\\S.)+(\\S)+")) {
try {
bot.send(Lookup.lookup(args));
} catch (UnknownHostException ignore) {
if (args.matches(
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\."
+ "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)")) {
try {
final String[] lines = Lookup.whois(args);
if ((lines != null) && (lines.length > 0)) {
String line;
for (final String rawLine : lines) {
line = rawLine.trim();
if ((line.length() > 0) && (line.charAt(0) != '#')) {
bot.send(line);
}
}
} else {
bot.send("Unknown host.");
}
} catch (IOException ioe) {
bot.getLogger().debug("Unable to perform whois IP lookup: {}", args, ioe);
bot.send("Unable to perform whois IP lookup: " + ioe.getMessage());
}
} else {
bot.send("Unknown host.");
}
}
} else {
helpResponse(sender, true);
}
}
}

View file

@ -0,0 +1,169 @@
/*
* Lookup.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import org.apache.commons.net.whois.WhoisClient
import java.io.IOException
import java.net.InetAddress
import java.net.UnknownHostException
/**
* The Lookup module.
*/
class Lookup(bot: Mobibot) : AbstractModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
if (args.matches("(\\S.)+(\\S)+".toRegex())) {
with(bot) {
try {
send(lookup(args))
} catch (ignore: UnknownHostException) {
if (args.matches(
("(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)").toRegex()
)
) {
try {
val lines = whois(args)
if (lines.isNotEmpty()) {
var line: String
for (rawLine in lines) {
line = rawLine.trim()
if (line.isNotEmpty() && line[0] != '#') {
send(line)
}
}
} else {
send("Unknown host.")
}
} catch (ioe: IOException) {
logger.debug("Unable to perform whois IP lookup: {}", args, ioe)
send("Unable to perform whois IP lookup: ${ioe.message}")
}
} else {
send("Unknown host.")
}
}
}
} else {
helpResponse(sender, true)
}
}
companion object {
/**
* The whois default host.
*/
const val WHOIS_HOST = "whois.arin.net"
// Lookup command
private const val LOOKUP_CMD = "lookup"
/**
* Performs a DNS lookup on the specified query.
*/
@JvmStatic
@Throws(UnknownHostException::class)
fun lookup(query: String): String {
val buffer = StringBuilder()
val results = InetAddress.getAllByName(query)
var hostInfo: String
for (result in results) {
if (result.hostAddress == query) {
hostInfo = result.hostName
if (hostInfo == query) {
throw UnknownHostException()
}
} else {
hostInfo = result.hostAddress
}
if (buffer.isNotEmpty()) {
buffer.append(", ")
}
buffer.append(hostInfo)
}
return buffer.toString()
}
/**
* Performs a whois IP query.
*
* @param query The IP address.
* @return The IP whois data, if any.
* @throws java.io.IOException If a connection error occurs.
*/
@Throws(IOException::class)
private fun whois(query: String): Array<String> {
return whois(query, WHOIS_HOST)
}
/**
* Performs a whois IP query.
*/
@JvmStatic
@Throws(IOException::class)
fun whois(query: String, host: String): Array<String> {
val whoisClient = WhoisClient()
val lines: Array<String>
with(whoisClient) {
try {
defaultTimeout = Constants.CONNECT_TIMEOUT
connect(host)
soTimeout = Constants.CONNECT_TIMEOUT
setSoLinger(false, 0)
lines = if (WHOIS_HOST == host) {
query("n - $query").split("\n").toTypedArray()
} else {
query(query).split("\n").toTypedArray()
}
} finally {
disconnect()
}
}
return lines
}
}
init {
commands.add(LOOKUP_CMD)
help.add("To perform a DNS lookup query:")
help.add(Utils.helpIndent("%c $LOOKUP_CMD <ip address or hostname>"))
}
}

View file

@ -1,117 +0,0 @@
/*
* ModuleException.java
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
/**
* The <code>ModuleException</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class ModuleException extends Exception {
private static final long serialVersionUID = 1L;
private final String debugMessage;
/**
* Creates a new exception.
*
* @param message The exception message.
*/
ModuleException(final String message) {
super(message);
this.debugMessage = message;
}
/**
* Creates a new exception.
*
* @param debugMessage The debug message.
* @param message The exception message.
* @param cause The cause.
*/
ModuleException(final String debugMessage, final String message, final Throwable cause) {
super(message, cause);
this.debugMessage = debugMessage;
}
/**
* Creates a new exception.
*
* @param debugMessage The debug message.
* @param message The exception message.
*/
ModuleException(final String debugMessage, final String message) {
super(message);
this.debugMessage = debugMessage;
}
/**
* Returns the debug message.
*
* @return The debug message.
*/
String getDebugMessage() {
return debugMessage;
}
/**
* Return the sanitized message (e.g. remove API keys, etc.)
*
* @param sanitize The words to sanitize.
* @return The sanitized message.
*/
String getSanitizedMessage(final String... sanitize) {
final String[] obfuscate = new String[sanitize.length];
for (int i = 0; i < sanitize.length; i++) {
obfuscate[i] = Utils.obfuscate(sanitize[i]);
}
return getCause().getClass().getName() + ": " + StringUtils.replaceEach(getCause().getMessage(),
sanitize,
obfuscate);
}
/**
* Return <code>true</code> if the exception has a cause.
*
* @return <code>true</code> or <code>false</code>
*/
@SuppressWarnings("unused")
boolean hasCause() {
return getCause() != null;
}
}

View file

@ -0,0 +1,89 @@
/*
* ModuleException.kit
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Utils
import org.apache.commons.lang3.StringUtils
/**
* The `ModuleException` class.
*/
class ModuleException : Exception {
/**
* Returns the debug message.
*/
val debugMessage: String?
/**
* Creates a new exception.
*/
constructor(message: String?) : super(message) {
debugMessage = message
}
/**
* Creates a new exception.
*/
constructor(debugMessage: String?, message: String?, cause: Throwable?) : super(message, cause) {
this.debugMessage = debugMessage
}
/**
* Creates a new exception.
*/
constructor(debugMessage: String?, message: String?) : super(message) {
this.debugMessage = debugMessage
}
/**
* Return the sanitized message (e.g. remove API keys, etc.)
*/
fun getSanitizedMessage(vararg sanitize: String?): String {
val obfuscate = arrayOfNulls<String>(sanitize.size)
for (i in sanitize.indices) {
obfuscate[i] = Utils.obfuscate(sanitize[i])
}
return cause!!.javaClass.name + ": " + StringUtils.replaceEach(cause.message, sanitize, obfuscate)
}
/**
* Return `true` if the exception has a cause.
*/
@Suppress("unused")
fun hasCause(): Boolean {
return cause != null
}
companion object {
private const val serialVersionUID = 1L
}
}

View file

@ -1,95 +0,0 @@
/*
* Ping.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
/**
* The Ping module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2016-07-02
* @since 1.0
*/
public class Ping extends AbstractModule {
/**
* The ping responses.
*/
static final List<String> PINGS =
Arrays.asList(
"is barely alive.",
"is trying to stay awake.",
"has gone fishing.",
"is somewhere over the rainbow.",
"has fallen and can't get up.",
"is running. You better go chase it.",
"has just spontaneously combusted.",
"is talking to itself... don't interrupt. That's rude.",
"is bartending at an AA meeting.",
"is hibernating.",
"is saving energy: apathetic mode activated.",
"is busy. Go away!");
/**
* The ping command.
*/
private static final String PING_CMD = "ping";
/**
* The default constructor.
*/
public Ping(final Mobibot bot) {
super(bot);
commands.add(PING_CMD);
help.add("To ping the bot:");
help.add(Utils.helpIndent("%c " + PING_CMD));
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom();
bot.action(PINGS.get(r.nextInt(PINGS.size())));
}
}

View file

@ -0,0 +1,90 @@
/*
* Ping.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import kotlin.random.Random
/**
* The Ping module.
*/
class Ping(bot: Mobibot) : AbstractModule(bot) {
/**
* {@inheritDoc}
*/
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
bot.action(randomPing())
}
companion object {
/**
* The ping responses.
*/
@JvmField
val PINGS = listOf(
"is barely alive.",
"is trying to stay awake.",
"has gone fishing.",
"is somewhere over the rainbow.",
"has fallen and can't get up.",
"is running. You better go chase it.",
"has just spontaneously combusted.",
"is talking to itself... don't interrupt. That's rude.",
"is bartending at an AA meeting.",
"is hibernating.",
"is saving energy: apathetic mode activated.",
"is busy. Go away!"
)
@JvmStatic
fun randomPing(): String {
return PINGS[Random.nextInt(PINGS.size)]
}
/**
* The ping command.
*/
private const val PING_CMD = "ping"
}
init {
commands.add(PING_CMD)
help.add("To ping the bot:")
help.add(Utils.helpIndent("%c $PING_CMD"))
}
}

View file

@ -34,9 +34,6 @@ package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.green
import net.thauvin.erik.mobibot.Utils.red
import kotlin.random.Random
@ -96,21 +93,23 @@ class RockPaperScissors(bot: Mobibot) : AbstractModule(bot) {
}
}
override fun commandResponse(sender: String, cmd: String, args: String?, isPrivate: Boolean) {
override fun commandResponse(sender: String, cmd: String, args: String, isPrivate: Boolean) {
val hand = Hands.valueOf(cmd.toUpperCase())
val botHand = Hands.values()[Random.nextInt(0, Hands.values().size)]
when {
hand == botHand -> {
bot.send("${green(hand.name)} vs. ${green(botHand.name)}")
bot.action("tied.")
}
hand.beats(botHand) -> {
bot.send("${green(hand.name)} ${bold(hand.action)} ${red(botHand.name)}")
bot.action("lost.")
}
else -> {
bot.send("${green(botHand.name)} ${bold(botHand.action)} ${red(hand.name)}")
bot.action("wins.")
with(bot) {
when {
hand == botHand -> {
send("${Utils.green(hand.name)} vs. ${Utils.green(botHand.name)}")
action("tied.")
}
hand.beats(botHand) -> {
send("${Utils.green(hand.name)} ${Utils.bold(hand.action)} ${Utils.red(botHand.name)}")
action("lost.")
}
else -> {
send("${Utils.green(botHand.name)} ${Utils.bold(botHand.action)} ${Utils.red(hand.name)}")
action("wins.")
}
}
}
}

View file

@ -1,218 +0,0 @@
/*
* StockQuote.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.NoticeMessage;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* The StockQuote module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Feb 7, 2004
* @since 1.0
*/
public final class StockQuote extends ThreadedModule {
/**
* The Alpha Advantage property key.
*/
static final String ALPHAVANTAGE_API_KEY_PROP = "alphavantage-api-key";
/**
* The Invalid Symbol error string.
*/
static final String INVALID_SYMBOL = "Invalid symbol.";
// Alpha Advantage URL
private static final String ALAPHAVANTAGE_URL = "https://www.alphavantage.co/query?function=";
// Quote command
private static final String STOCK_CMD = "stock";
/**
* Creates a new {@link StockQuote} instance.
*/
public StockQuote(final Mobibot bot) {
super(bot);
commands.add(STOCK_CMD);
help.add("To retrieve a stock quote:");
help.add(Utils.helpIndent("%c " + STOCK_CMD + " <symbol|keywords>"));
initProperties(ALPHAVANTAGE_API_KEY_PROP);
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
justification = "false positive?")
private static JSONObject getJsonResponse(final String response, final String debugMessage)
throws ModuleException {
if (StringUtils.isNotBlank(response)) {
final JSONObject json = new JSONObject(response);
try {
final String info = json.getString("Information");
if (!info.isEmpty()) {
throw new ModuleException(debugMessage, Utils.unescapeXml(info));
}
} catch (JSONException ignore) {
// Do nothing
}
try {
final String error = json.getString("Note");
if (!error.isEmpty()) {
throw new ModuleException(debugMessage, Utils.unescapeXml(error));
}
} catch (JSONException ignore) {
// Do nothing
}
try {
final String error = json.getString("Error Message");
if (!error.isEmpty()) {
throw new ModuleException(debugMessage, Utils.unescapeXml(error));
}
} catch (JSONException ignore) {
// Do nothing
}
return json;
} else {
throw new ModuleException(debugMessage, "Empty Response.");
}
}
/**
* Retrieves a stock quote.
*
* @param symbol The stock symbol.
* @return The {@link Message} array containing the stock quote.
* @throws ModuleException If an errors occurs.
*/
@SuppressWarnings({ "PMD.CloseResource" })
static List<Message> getQuote(final String symbol, final String apiKey) throws ModuleException {
if (StringUtils.isBlank(apiKey)) {
throw new ModuleException(StringUtils.capitalize(STOCK_CMD) + " is disabled. The API key is missing.");
}
if (StringUtils.isNotBlank(symbol)) {
final String debugMessage = "getQuote(" + symbol + ')';
final ArrayList<Message> messages = new ArrayList<>();
String response;
try {
// Search for symbol/keywords
response = Utils.urlReader(new URL(
ALAPHAVANTAGE_URL + "SYMBOL_SEARCH&keywords=" + Utils.encodeUrl(symbol) + "&apikey="
+ Utils.encodeUrl(apiKey)));
JSONObject json = getJsonResponse(response, debugMessage);
final JSONArray symbols = json.getJSONArray("bestMatches");
if (symbols.isEmpty()) {
messages.add(new ErrorMessage(INVALID_SYMBOL));
return messages;
}
final JSONObject symbolInfo = symbols.getJSONObject(0);
// Get quote for symbol
response = Utils.urlReader(new URL(
ALAPHAVANTAGE_URL + "GLOBAL_QUOTE&symbol=" + Utils.encodeUrl(symbolInfo.getString("1. symbol"))
+ "&apikey=" + Utils.encodeUrl(apiKey)));
json = getJsonResponse(response, debugMessage);
final JSONObject quote = json.getJSONObject("Global Quote");
if (quote.isEmpty()) {
messages.add(new ErrorMessage(INVALID_SYMBOL));
return messages;
}
messages.add(new PublicMessage(
"Symbol: " + Utils.unescapeXml(quote.getString("01. symbol")) + " [" + Utils.unescapeXml(
symbolInfo.getString("2. name") + ']')));
messages.add(new PublicMessage(" Price: " + Utils.unescapeXml(quote.getString("05. price"))));
messages.add(new PublicMessage(
" Previous: "
+ Utils.unescapeXml(quote.getString("08. previous close"))));
messages.add(new NoticeMessage(" Open: " + Utils.unescapeXml(quote.getString("02. open"))));
messages.add(new NoticeMessage(" High: " + Utils.unescapeXml(quote.getString("03. high"))));
messages.add(new NoticeMessage(" Low: " + Utils.unescapeXml(quote.getString("04. low"))));
messages.add(new NoticeMessage(" Volume: " + Utils.unescapeXml(quote.getString("06. volume"))));
messages.add(new NoticeMessage(
" Latest: " + Utils.unescapeXml(quote.getString("07. latest trading day"))));
messages.add(new NoticeMessage(
" Change: " + Utils.unescapeXml(quote.getString("09. change")) + " [" + Utils.unescapeXml(
quote.getString("10. change percent")) + ']'));
} catch (IOException | NullPointerException e) {
throw new ModuleException(debugMessage, "An error has occurred retrieving a stock quote.", e);
}
return messages;
} else {
throw new ModuleException(INVALID_SYMBOL);
}
}
/**
* Returns the specified stock quote from Alpha Avantage.
*/
@Override
void run(final String sender, final String cmd, final String symbol, final boolean isPrivate) {
if (StringUtils.isNotBlank(symbol)) {
try {
final List<Message> messages = getQuote(symbol, properties.get(ALPHAVANTAGE_API_KEY_PROP));
for (final Message msg : messages) {
bot.send(sender, msg);
}
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(e.getMessage());
}
} else {
helpResponse(sender, isPrivate);
}
}
}

View file

@ -0,0 +1,214 @@
/*
* StockQuote.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import net.thauvin.erik.mobibot.msg.PublicMessage
import org.apache.commons.lang3.StringUtils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.URL
import java.util.*
/**
* The StockQuote module.
*/
class StockQuote(bot: Mobibot) : ThreadedModule(bot) {
/**
* Returns the specified stock quote from Alpha Avantage.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
if (StringUtils.isNotBlank(args)) {
try {
val messages = getQuote(args, properties[ALPHAVANTAGE_API_KEY_PROP])
for (msg in messages) {
send(sender, msg)
}
} catch (e: ModuleException) {
logger.warn(e.debugMessage, e)
send(e.message)
}
} else {
helpResponse(sender, isPrivate)
}
}
}
companion object {
/**
* The Alpha Advantage property key.
*/
const val ALPHAVANTAGE_API_KEY_PROP = "alphavantage-api-key"
/**
* The Invalid Symbol error string.
*/
const val INVALID_SYMBOL = "Invalid symbol."
// Alpha Advantage URL
private const val ALAPHAVANTAGE_URL = "https://www.alphavantage.co/query?function="
// Quote command
private const val STOCK_CMD = "stock"
@Throws(ModuleException::class)
private fun getJsonResponse(response: String, debugMessage: String): JSONObject {
return if (StringUtils.isNotBlank(response)) {
val json = JSONObject(response)
try {
val info = json.getString("Information")
if (info.isNotEmpty()) {
throw ModuleException(debugMessage, Utils.unescapeXml(info))
}
} catch (ignore: JSONException) {
// Do nothing
}
try {
var error = json.getString("Note")
if (error.isNotEmpty()) {
throw ModuleException(debugMessage, Utils.unescapeXml(error))
}
error = json.getString("Error Message")
if (error.isNotEmpty()) {
throw ModuleException(debugMessage, Utils.unescapeXml(error))
}
} catch (ignore: JSONException) {
// Do nothing
}
json
} else {
throw ModuleException(debugMessage, "Empty Response.")
}
}
/**
* Retrieves a stock quote.
*/
@JvmStatic
@Throws(ModuleException::class)
fun getQuote(symbol: String, apiKey: String?): List<Message> {
if (StringUtils.isBlank(apiKey)) {
throw ModuleException("${STOCK_CMD.capitalize()} is disabled. The API key is missing.")
}
return if (StringUtils.isNotBlank(symbol)) {
val debugMessage = "getQuote($symbol)"
val messages = ArrayList<Message>()
var response: String
try {
with(messages) {
// Search for symbol/keywords
response = Utils.urlReader(
URL(
"${ALAPHAVANTAGE_URL}SYMBOL_SEARCH&keywords=" + Utils.encodeUrl(symbol) + "&apikey="
+ Utils.encodeUrl(apiKey)
)
)
var json = getJsonResponse(response, debugMessage)
val symbols = json.getJSONArray("bestMatches")
if (symbols.isEmpty) {
messages.add(ErrorMessage(INVALID_SYMBOL))
} else {
val symbolInfo = symbols.getJSONObject(0)
// Get quote for symbol
response = Utils.urlReader(
URL(
"${ALAPHAVANTAGE_URL}GLOBAL_QUOTE&symbol="
+ Utils.encodeUrl(symbolInfo.getString("1. symbol"))
+ "&apikey=" + Utils.encodeUrl(apiKey)
)
)
json = getJsonResponse(response, debugMessage)
val quote = json.getJSONObject("Global Quote")
if (quote.isEmpty) {
add(ErrorMessage(INVALID_SYMBOL))
return messages
}
add(
PublicMessage(
"Symbol: " + Utils.unescapeXml(quote.getString("01. symbol"))
+ " [" + Utils.unescapeXml(symbolInfo.getString("2. name")) + ']'
)
)
add(PublicMessage(" Price: " + Utils.unescapeXml(quote.getString("05. price"))))
add(
PublicMessage(
" Previous: " + Utils.unescapeXml(quote.getString("08. previous close"))
)
)
add(NoticeMessage(" Open: " + Utils.unescapeXml(quote.getString("02. open"))))
add(NoticeMessage(" High: " + Utils.unescapeXml(quote.getString("03. high"))))
add(NoticeMessage(" Low: " + Utils.unescapeXml(quote.getString("04. low"))))
add(
NoticeMessage(
" Volume: " + Utils.unescapeXml(quote.getString("06. volume"))
)
)
add(
NoticeMessage(
" Latest: "
+ Utils.unescapeXml(quote.getString("07. latest trading day"))
)
)
add(
NoticeMessage(
" Change: " + Utils.unescapeXml(quote.getString("09. change"))
+ " [" + Utils.unescapeXml(quote.getString("10. change percent")) + ']'
)
)
}
}
} catch (e: IOException) {
throw ModuleException(debugMessage, "An IO error has occurred retrieving a stock quote.", e)
} catch (e: NullPointerException) {
throw ModuleException(debugMessage, "An error has occurred retrieving a stock quote.", e)
}
messages
} else {
throw ModuleException(INVALID_SYMBOL)
}
}
}
init {
commands.add(STOCK_CMD)
help.add("To retrieve a stock quote:")
help.add(Utils.helpIndent("%c $STOCK_CMD <symbol|keywords>"))
initProperties(ALPHAVANTAGE_API_KEY_PROP)
}
}

View file

@ -29,40 +29,34 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Mobibot
/**
* The <code>ThreadedModule</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-03
* @since 1.0
* The `ThreadedModule` class.
*/
public abstract class ThreadedModule extends AbstractModule {
ThreadedModule(final Mobibot bot) {
super(bot);
}
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (isEnabled() && args.length() > 0) {
new Thread(() -> run(sender, cmd, args, isPrivate)).start();
abstract class ThreadedModule(bot: Mobibot) : AbstractModule(bot) {
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
if (isEnabled && args.isNotEmpty()) {
Thread { run(sender, cmd, args, isPrivate) }.start()
} else {
helpResponse(sender, isPrivate);
helpResponse(sender, isPrivate)
}
}
/**
* Runs the thread.
*/
abstract void run(String sender,
@SuppressWarnings("unused") String cmd,
String args,
boolean isPrivate);
abstract fun run(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
)
}

View file

@ -1,279 +0,0 @@
/*
* Twitter.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Constants;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.ReleaseInfo;
import net.thauvin.erik.mobibot.TwitterTimer;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.commands.links.LinksMgr;
import net.thauvin.erik.mobibot.entries.EntryLink;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.NoticeMessage;
import org.apache.commons.lang3.StringUtils;
import twitter4j.DirectMessage;
import twitter4j.Status;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;
import java.util.HashSet;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* The Twitter module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Sept 10, 2008
* @since 1.0
*/
public final class Twitter extends ThreadedModule {
// Property keys
static final String AUTOPOST_PROP = "twitter-auto-post";
static final String CONSUMER_KEY_PROP = "twitter-consumerKey";
static final String CONSUMER_SECRET_PROP = "twitter-consumerSecret";
static final String HANDLE_PROP = "twitter-handle";
static final String TOKEN_PROP = "twitter-token";
static final String TOKEN_SECRET_PROP = "twitter-tokenSecret";
// Twitter command
private static final String TWITTER_CMD = "twitter";
// Twitter auto-posts.
private final Set<Integer> entries = new HashSet<>();
/**
* Creates a new {@link Twitter} instance.
*/
public Twitter(final Mobibot bot) {
super(bot);
commands.add(TWITTER_CMD);
help.add("To post to Twitter:");
help.add(Utils.helpIndent("%c " + TWITTER_CMD + " <message>"));
properties.put(AUTOPOST_PROP, "false");
initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP);
}
/**
* Posts on Twitter.
*
* @param consumerKey The consumer key.
* @param consumerSecret The consumer secret.
* @param token The token.
* @param tokenSecret The token secret.
* @param handle The Twitter handle (dm) or nickname.
* @param message The message to post.
* @param isDm The direct message flag.
* @return The confirmation {@link Message}.
* @throws ModuleException If an error occurs while posting.
*/
static Message twitterPost(final String consumerKey,
final String consumerSecret,
final String token,
final String tokenSecret,
final String handle,
final String message,
final boolean isDm) throws ModuleException {
try {
final ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true).setOAuthConsumerKey(consumerKey).setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(token).setOAuthAccessTokenSecret(tokenSecret);
final TwitterFactory tf = new TwitterFactory(cb.build());
final twitter4j.Twitter twitter = tf.getInstance();
if (!isDm) {
final Status status = twitter.updateStatus(message);
return new NoticeMessage("You message was posted to https://twitter.com/" + twitter.getScreenName()
+ "/statuses/" + status.getId());
} else {
final DirectMessage dm = twitter.sendDirectMessage(handle, message);
return new NoticeMessage(dm.getText());
}
} catch (Exception e) {
throw new ModuleException("twitterPost(" + message + ")", "An error has occurred: " + e.getMessage(), e);
}
}
/**
* Add an entry to be posted on Twitter.
*
* @param index The entry index.
*/
public final void addEntry(final int index) {
entries.add(index);
}
public final int entriesCount() {
return entries.size();
}
public String getHandle() {
return properties.get(HANDLE_PROP);
}
public final boolean hasEntry(final int index) {
return entries.contains(index);
}
public boolean isAutoPost() {
return isEnabled() && Boolean.parseBoolean(properties.get(AUTOPOST_PROP));
}
@Override
boolean isValidProperties() {
for (final String s : getPropertyKeys()) {
if (!AUTOPOST_PROP.equals(s) && !HANDLE_PROP.equals(s) && StringUtils.isBlank(properties.get(s))) {
return false;
}
}
return true;
}
/**
* Send a notification to the registered Twitter handle.
*
* @param msg The twitter message.
*/
public final void notification(final String msg) {
if (isEnabled() && isNotBlank(getHandle())) {
new Thread(() -> {
try {
post(String.format(msg, bot.getName(), ReleaseInfo.VERSION, bot.getChannel()), true);
} catch (ModuleException e) {
bot.getLogger().warn("Failed to notify @{}: {}", getHandle(), msg, e);
}
}).start();
}
}
/**
* Posts on Twitter.
*
* @param message The message to post.
* @param isDm The direct message flag.
* @throws ModuleException If an error occurs while posting.
*/
public void post(final String message, final boolean isDm)
throws ModuleException {
post(properties.get(HANDLE_PROP), message, isDm);
}
/**
* Posts on Twitter.
*
* @param handle The Twitter handle (dm) or nickname.
* @param message The message to post.
* @param isDm The direct message flag.
* @return The {@link Message} to send back.
* @throws ModuleException If an error occurs while posting.
*/
public Message post(final String handle, final String message, final boolean isDm)
throws ModuleException {
return twitterPost(properties.get(CONSUMER_KEY_PROP),
properties.get(CONSUMER_SECRET_PROP),
properties.get(TOKEN_PROP),
properties.get(TOKEN_SECRET_PROP),
handle,
message,
isDm);
}
/**
* Post an entry to twitter.
*
* @param index The post entry index.
*/
@SuppressFBWarnings("SUI_CONTAINS_BEFORE_REMOVE")
public final void postEntry(final int index) {
if (isAutoPost() && hasEntry(index) && LinksMgr.getEntriesCount() >= index) {
final EntryLink entry = LinksMgr.getEntry(index);
final String msg =
entry.getTitle() + ' ' + entry.getLink() + " via " + entry.getNick() + " on " + bot.getChannel();
new Thread(() -> {
try {
if (bot.getLogger().isDebugEnabled()) {
bot.getLogger().debug("Posting {}{} to Twitter.", Constants.LINK_CMD, index + 1);
}
post(msg, false);
} catch (ModuleException e) {
bot.getLogger().warn("Failed to post entry on Twitter.", e);
}
}).start();
removeEntry(index);
}
}
public void queueEntry(final int index) {
if (isAutoPost()) {
addEntry(index);
bot.getLogger().debug("Scheduling ${Constants.LINK_CMD}${index + 1} for posting on Twitter.");
bot.getTimer().schedule(new TwitterTimer(bot, index), Constants.TIMER_DELAY * 60L * 1000L);
}
}
public final void removeEntry(final int index) {
entries.remove(index);
}
/**
* Posts to twitter.
*/
@Override
void run(final String sender, final String cmd, final String message, final boolean isPrivate) {
try {
bot.send(sender,
post(sender, message + " (by " + sender + " on " + bot.getChannel() + ')', false).getMsg(),
isPrivate);
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage(), isPrivate);
}
}
/**
* Post all the entries to Twitter on shutdown.
*/
public final void shutdown() {
if (isAutoPost()) {
for (final int index : entries) {
postEntry(index);
}
}
}
}

View file

@ -0,0 +1,244 @@
/*
* Twitter.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.TwitterTimer
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.entriesCount
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.getEntry
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import org.apache.commons.lang3.StringUtils
import twitter4j.TwitterException
import twitter4j.TwitterFactory
import twitter4j.conf.ConfigurationBuilder
import java.util.*
/**
* The Twitter module.
*/
class Twitter(bot: Mobibot) : ThreadedModule(bot) {
// Twitter auto-posts.
private val entries: MutableSet<Int> = HashSet()
/**
* Add an entry to be posted on Twitter.
*/
private fun addEntry(index: Int) {
entries.add(index)
}
fun entriesCount(): Int {
return entries.size
}
private val handle: String?
get() = properties[HANDLE_PROP]
private fun hasEntry(index: Int): Boolean {
return entries.contains(index)
}
val isAutoPost: Boolean
get() = isEnabled && properties[AUTOPOST_PROP].toBoolean()
override val isValidProperties: Boolean
get() {
for (s in propertyKeys) {
if (AUTOPOST_PROP != s && HANDLE_PROP != s && properties[s]!!.isBlank()) {
return false
}
}
return true
}
/**
* Send a notification to the registered Twitter handle.
*/
fun notification(msg: String) {
with(bot) {
if (isEnabled && StringUtils.isNotBlank(handle)) {
Thread {
try {
post(message = msg, isDm = true)
if (logger.isDebugEnabled) {
logger.debug("Notified @{}: {}", handle, msg)
}
} catch (e: ModuleException) {
logger.warn("Failed to notify @{}: {}", handle, msg, e)
}
}.start()
}
}
}
/**
* Posts on Twitter.
*/
@Throws(ModuleException::class)
fun post(handle: String = "${properties[HANDLE_PROP]}", message: String, isDm: Boolean): Message {
return twitterPost(
properties[CONSUMER_KEY_PROP],
properties[CONSUMER_SECRET_PROP],
properties[TOKEN_PROP],
properties[TOKEN_SECRET_PROP],
handle,
message,
isDm
)
}
/**
* Post an entry to twitter.
*/
fun postEntry(index: Int) {
with(bot) {
if (isAutoPost && hasEntry(index) && entriesCount >= index) {
val entry = getEntry(index)
val msg = "${entry.title} ${entry.link} via ${entry.nick} on $channel"
Thread {
try {
if (logger.isDebugEnabled) {
logger.debug("Posting {}{} to Twitter.", Constants.LINK_CMD, index + 1)
}
post(message = msg, isDm = false)
} catch (e: ModuleException) {
logger.warn("Failed to post entry on Twitter.", e)
}
}.start()
removeEntry(index)
}
}
}
fun queueEntry(index: Int) {
if (isAutoPost) {
addEntry(index)
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Scheduling {}{} for posting on Twitter.", Constants.LINK_CMD, index + 1)
}
bot.timer.schedule(TwitterTimer(bot, index), Constants.TIMER_DELAY * 60L * 1000L)
}
}
fun removeEntry(index: Int) {
entries.remove(index)
}
/**
* Posts to twitter.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
try {
send(
sender,
post(sender, "$args (by $sender on $channel)", false).msg,
isPrivate
)
} catch (e: ModuleException) {
logger.warn(e.debugMessage, e)
send(sender, e.message, isPrivate)
}
}
}
/**
* Post all the entries to Twitter on shutdown.
*/
fun shutdown() {
if (isAutoPost) {
for (index in entries) {
postEntry(index)
}
}
}
companion object {
// Property keys
const val AUTOPOST_PROP = "twitter-auto-post"
const val CONSUMER_KEY_PROP = "twitter-consumerKey"
const val CONSUMER_SECRET_PROP = "twitter-consumerSecret"
const val HANDLE_PROP = "twitter-handle"
const val TOKEN_PROP = "twitter-token"
const val TOKEN_SECRET_PROP = "twitter-tokenSecret"
// Twitter command
private const val TWITTER_CMD = "twitter"
/**
* Posts on Twitter.
*/
@JvmStatic
@Throws(ModuleException::class)
fun twitterPost(
consumerKey: String?,
consumerSecret: String?,
token: String?,
tokenSecret: String?,
handle: String?,
message: String,
isDm: Boolean
): Message {
return try {
val cb = ConfigurationBuilder()
cb.setDebugEnabled(true)
.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(token).setOAuthAccessTokenSecret(tokenSecret)
val tf = TwitterFactory(cb.build())
val twitter = tf.instance
if (!isDm) {
val status = twitter.updateStatus(message)
NoticeMessage(
"You message was posted to https://twitter.com/${twitter.screenName}/statuses/${status.id}"
)
} else {
val dm = twitter.sendDirectMessage(handle, message)
NoticeMessage(dm.text)
}
} catch (e: TwitterException) {
throw ModuleException("twitterPost($message)", "An error has occurred: ${e.message}", e)
}
}
}
init {
commands.add(TWITTER_CMD)
help.add("To post to Twitter:")
help.add(Utils.helpIndent("%c $TWITTER_CMD <message>"))
properties[AUTOPOST_PROP] = "false"
initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP)
}
}

View file

@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.jetbrains.annotations.NotNull;
import java.security.SecureRandom;
@ -71,9 +72,9 @@ public final class War extends AbstractModule {
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
public void commandResponse(@NotNull final String sender,
@NotNull final String cmd,
@NotNull final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom();
@ -84,20 +85,21 @@ public final class War extends AbstractModule {
i = r.nextInt(WAR_DECK.length);
y = r.nextInt(WAR_DECK.length);
bot.send(sender + " drew the " + bold(WAR_DECK[i]) + " of " + bold(WAR_SUITS[r.nextInt(WAR_SUITS.length)]));
bot.action("drew the " + bold(WAR_DECK[y]) + " of " + bold(WAR_SUITS[r.nextInt(WAR_SUITS.length)]));
getBot().send(sender + " drew the " + bold(WAR_DECK[i]) + " of "
+ bold(WAR_SUITS[r.nextInt(WAR_SUITS.length)]));
getBot().action("drew the " + bold(WAR_DECK[y]) + " of " + bold(WAR_SUITS[r.nextInt(WAR_SUITS.length)]));
if (i != y) {
break;
}
bot.send("This means " + bold("WAR") + '!');
getBot().send("This means " + bold("WAR") + '!');
}
if (i < y) {
bot.action("lost.");
getBot().action("lost.");
} else {
bot.action("wins.");
getBot().action("wins.");
}
}
}

View file

@ -1,232 +0,0 @@
/*
* Weather2.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import net.aksingh.owmjapis.api.APIException;
import net.aksingh.owmjapis.core.OWM;
import net.aksingh.owmjapis.model.CurrentWeather;
import net.aksingh.owmjapis.model.param.Main;
import net.aksingh.owmjapis.model.param.Weather;
import net.aksingh.owmjapis.model.param.Wind;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.NoticeMessage;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors;
import java.util.ArrayList;
import java.util.List;
import static net.thauvin.erik.mobibot.Utils.bold;
/**
* The <code>Weather2</code> module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2017-04-02
* @since 1.0
*/
public class Weather2 extends ThreadedModule {
/**
* The OpenWeatherMap API Key property.
*/
static final String OWM_API_KEY_PROP = "owm-api-key";
// Weather command
private static final String WEATHER_CMD = "weather";
/**
* Creates a new {@link Weather2} instance.
*/
public Weather2(final Mobibot bot) {
super(bot);
commands.add(WEATHER_CMD);
help.add("To display weather information:");
help.add(Utils.helpIndent("%c " + WEATHER_CMD + " <city> [, <country code>]"));
help.add("For example:");
help.add(Utils.helpIndent("%c " + WEATHER_CMD + " paris, fr"));
help.add("The default ISO 3166 country code is " + bold("US") + ". Zip codes supported in most countries.");
initProperties(OWM_API_KEY_PROP);
}
private static OWM.Country getCountry(final String countryCode) {
for (final OWM.Country c : OWM.Country.values()) {
if (c.name().equalsIgnoreCase(countryCode)) {
return c;
}
}
return OWM.Country.UNITED_STATES;
}
@SuppressWarnings("AvoidEscapedUnicodeCharacters")
private static String getTemps(final Double d) {
final double c = (d - 32) * 5 / 9;
return Math.round(d) + " °F, " + Math.round(c) + " °C";
}
/**
* Retrieves the weather data.
*
* <ul>
* <li>98204</li>
* <li>London, UK</li>
* </ul>
*
* @param query The query.
* @param apiKey The API key.
* @return The {@link Message} array containing the weather data.
* @throws ModuleException If an error occurs while retrieving the weather data.
*/
static List<Message> getWeather(final String query, final String apiKey) throws ModuleException {
if (StringUtils.isBlank(apiKey)) {
throw new ModuleException(StringUtils.capitalize(WEATHER_CMD) + " is disabled. The API key is missing.");
}
final OWM owm = new OWM(apiKey);
final ArrayList<Message> messages = new ArrayList<>();
owm.setUnit(OWM.Unit.IMPERIAL);
if (StringUtils.isNotBlank(query)) {
final String[] argv = query.split(",");
if (argv.length >= 1 && argv.length <= 2) {
final String country;
final String city = argv[0].trim();
if (argv.length > 1 && StringUtils.isNotBlank(argv[1])) {
country = argv[1].trim();
} else {
country = "US";
}
try {
final CurrentWeather cwd;
if (city.matches("\\d+")) {
cwd = owm.currentWeatherByZipCode(Integer.parseInt(city), getCountry(country));
} else {
cwd = owm.currentWeatherByCityName(city, getCountry(country));
}
if (cwd.hasCityName()) {
messages.add(new PublicMessage(
"City: " + cwd.getCityName() + " [" + StringUtils.upperCase(country) + "]"));
final Main main = cwd.getMainData();
if (main != null) {
if (main.hasTemp()) {
messages.add(new PublicMessage("Temperature: " + getTemps(main.getTemp())));
}
if (main.hasHumidity() && (main.getHumidity() != null)) {
messages.add(new NoticeMessage("Humidity: " + Math.round(main.getHumidity()) + "%"));
}
}
if (cwd.hasWindData()) {
final Wind w = cwd.getWindData();
if (w != null && w.hasSpeed()) {
messages.add(new NoticeMessage("Wind: " + wind(w.getSpeed())));
}
}
if (cwd.hasWeatherList()) {
final StringBuilder condition = new StringBuilder("Condition:");
final List<Weather> list = cwd.getWeatherList();
if (list != null) {
for (final Weather w : list) {
condition.append(' ')
.append(StringUtils.capitalize(w.getDescription()))
.append('.');
}
messages.add(new NoticeMessage(condition.toString()));
}
}
if (cwd.hasCityId() && cwd.getCityId() != null) {
if (cwd.getCityId() > 0) {
messages.add(new NoticeMessage("https://openweathermap.org/city/" + cwd.getCityId(),
Colors.GREEN));
} else {
messages.add(new NoticeMessage("https://openweathermap.org/find?q=" + Utils.encodeUrl(
city + ',' + StringUtils.upperCase(country)), Colors.GREEN));
}
}
}
} catch (APIException | NullPointerException e) {
throw new ModuleException("getWeather(" + query + ')', "Unable to perform weather lookup.", e);
}
}
}
if (messages.isEmpty()) {
messages.add(new ErrorMessage("Invalid syntax."));
}
return messages;
}
private static String wind(final Double w) {
final double kmh = w * 1.60934;
return Math.round(w) + " mph, " + Math.round(kmh) + " km/h";
}
/**
* Fetches the weather data from a specific city.
*/
@Override
void run(final String sender, final String cmd, final String args, final boolean isPrivate) {
if (StringUtils.isNotBlank(args)) {
try {
final List<Message> messages = getWeather(args, properties.get(OWM_API_KEY_PROP));
if (messages.get(0).isError()) {
helpResponse(sender, isPrivate);
} else {
for (final Message msg : messages) {
bot.send(sender, msg);
}
}
} catch (ModuleException e) {
bot.getLogger().debug(e.getDebugMessage(), e);
bot.send(e.getMessage());
}
} else {
helpResponse(sender, isPrivate);
}
}
}

View file

@ -0,0 +1,200 @@
/*
* Weather2.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.aksingh.owmjapis.api.APIException
import net.aksingh.owmjapis.core.OWM
import net.aksingh.owmjapis.core.OWM.Country
import net.aksingh.owmjapis.model.CurrentWeather
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import net.thauvin.erik.mobibot.msg.PublicMessage
import org.apache.commons.lang3.StringUtils
import org.jibble.pircbot.Colors
import java.util.*
import kotlin.math.roundToInt
/**
* The `Weather2` module.
*/
class Weather2(bot: Mobibot) : ThreadedModule(bot) {
/**
* Fetches the weather data from a specific city.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
if (StringUtils.isNotBlank(args)) {
try {
val messages = getWeather(args, properties[OWM_API_KEY_PROP])
if (messages[0].isError) {
helpResponse(sender, isPrivate)
} else {
for (msg in messages) {
bot.send(sender, msg)
}
}
} catch (e: ModuleException) {
bot.logger.debug(e.debugMessage, e)
bot.send(e.message)
}
} else {
helpResponse(sender, isPrivate)
}
}
companion object {
/**
* The OpenWeatherMap API Key property.
*/
const val OWM_API_KEY_PROP = "owm-api-key"
// Weather command
private const val WEATHER_CMD = "weather"
private fun getCountry(countryCode: String): Country {
for (c in Country.values()) {
if (c.name.equals(countryCode, ignoreCase = true)) {
return c
}
}
return Country.UNITED_STATES
}
private fun getTemps(d: Double?): String {
val c = (d!! - 32) * 5 / 9
return "${d.roundToInt()} °F, ${c.roundToInt()} °C"
}
/**
* Retrieves the weather data.
*/
@JvmStatic
@Throws(ModuleException::class)
fun getWeather(query: String, apiKey: String?): List<Message> {
if (StringUtils.isBlank(apiKey)) {
throw ModuleException("${WEATHER_CMD.capitalize()} is disabled. The API key is missing.")
}
val owm = OWM(apiKey!!)
val messages = ArrayList<Message>()
owm.unit = OWM.Unit.IMPERIAL
if (StringUtils.isNotBlank(query)) {
val argv = query.split(",").toTypedArray()
if (argv.size in 1..2) {
val city = argv[0].trim()
val country: String = if (argv.size > 1 && StringUtils.isNotBlank(argv[1])) {
argv[1].trim()
} else {
"US"
}
try {
val cwd: CurrentWeather = if (city.matches("\\d+".toRegex())) {
owm.currentWeatherByZipCode(city.toInt(), getCountry(country))
} else {
owm.currentWeatherByCityName(city, getCountry(country))
}
if (cwd.hasCityName()) {
messages.add(
PublicMessage("City: ${cwd.cityName} [${country.toUpperCase()}]")
)
with(cwd.mainData) {
if (this != null) {
if (hasTemp()) {
messages.add(PublicMessage("Temperature: ${getTemps(temp)}"))
}
if (hasHumidity() && humidity != null) {
messages.add(NoticeMessage("Humidity: ${(humidity!!).roundToInt()}%"))
}
}
}
if (cwd.hasWindData()) {
with(cwd.windData) {
if (this != null && hasSpeed()) {
messages.add(NoticeMessage("Wind: ${wind(speed)}"))
}
}
}
if (cwd.hasWeatherList()) {
val condition = StringBuilder("Condition:")
val list = cwd.weatherList
if (list != null) {
for (w in list) {
condition.append(' ').append(w!!.getDescription().capitalize()).append('.')
}
messages.add(NoticeMessage(condition.toString()))
}
}
if (cwd.hasCityId() && cwd.cityId != null) {
if (cwd.cityId!! > 0) {
messages.add(
NoticeMessage("https://openweathermap.org/city/${cwd.cityId}", Colors.GREEN)
)
} else {
messages.add(
NoticeMessage(
"https://openweathermap.org/find?q="
+ Utils.encodeUrl("$city,${country.toUpperCase()}"),
Colors.GREEN
)
)
}
}
}
} catch (e: APIException) {
throw ModuleException("getWeather($query)", "Unable to perform weather lookup.", e)
} catch (e: NullPointerException) {
throw ModuleException("getWeather($query)", "Unable to perform weather lookup.", e)
}
}
}
if (messages.isEmpty()) {
messages.add(ErrorMessage("Invalid syntax."))
}
return messages
}
private fun wind(w: Double?): String {
val kmh = w!! * 1.60934
return "${Math.round(w)} mph, ${kmh.roundToInt()} km/h"
}
}
init {
commands.add(WEATHER_CMD)
help.add("To display weather information:")
help.add(Utils.helpIndent("%c $WEATHER_CMD <city> [, <country code>]"))
help.add("For example:")
help.add(Utils.helpIndent("%c $WEATHER_CMD paris, fr"))
help.add("The default ISO 3166 country code is ${Utils.bold("US")}. Zip codes supported in most countries.")
initProperties(OWM_API_KEY_PROP)
}
}

View file

@ -1,247 +0,0 @@
/*
* WorldTime.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import org.apache.commons.lang3.StringUtils;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
/**
* The WorldTime module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-27
* @since 1.0
*/
@SuppressWarnings("PMD.UseConcurrentHashMap")
public final class WorldTime extends AbstractModule {
// Beats (Internet Time) keyword
private static final String BEATS_KEYWORD = ".beats";
// Supported countries
private static final Map<String, String> COUNTRIES_MAP;
// The Time command
private static final String TIME_CMD = "time";
static {
// Initialize the countries map
final Map<String, String> countries = new TreeMap<>();
countries.put("AE", "Asia/Dubai");
countries.put("AF", "Asia/Kabul");
countries.put("AQ", "Antarctica/South_Pole");
countries.put("AT", "Europe/Vienna");
countries.put("AU", "Australia/Sydney");
countries.put("AKST", "America/Anchorage");
countries.put("AKDT", "America/Anchorage");
countries.put("BE", "Europe/Brussels");
countries.put("BR", "America/Sao_Paulo");
countries.put("CA", "America/Montreal");
countries.put("CDT", "America/Chicago");
countries.put("CET", "CET");
countries.put("CH", "Europe/Zurich");
countries.put("CN", "Asia/Shanghai");
countries.put("CST", "America/Chicago");
countries.put("CU", "Cuba");
countries.put("DE", "Europe/Berlin");
countries.put("DK", "Europe/Copenhagen");
countries.put("EDT", "America/New_York");
countries.put("EG", "Africa/Cairo");
countries.put("ER", "Africa/Asmara");
countries.put("ES", "Europe/Madrid");
countries.put("EST", "America/New_York");
countries.put("FI", "Europe/Helsinki");
countries.put("FR", "Europe/Paris");
countries.put("GB", "Europe/London");
countries.put("GMT", "GMT");
countries.put("GR", "Europe/Athens");
countries.put("HK", "Asia/Hong_Kong");
countries.put("HST", "Pacific/Honolulu");
countries.put("IE", "Europe/Dublin");
countries.put("IL", "Asia/Tel_Aviv");
countries.put("IN", "Asia/Kolkata");
countries.put("IQ", "Asia/Baghdad");
countries.put("IR", "Asia/Tehran");
countries.put("IS", "Atlantic/Reykjavik");
countries.put("IT", "Europe/Rome");
countries.put("JM", "Jamaica");
countries.put("JP", "Asia/Tokyo");
countries.put("LY", "Africa/Tripoli");
countries.put("MA", "Africa/Casablanca");
countries.put("MDT", "America/Denver");
countries.put("MH", "Kwajalein");
countries.put("MQ", "America/Martinique");
countries.put("MST", "America/Denver");
countries.put("MX", "America/Mexico_City");
countries.put("NL", "Europe/Amsterdam");
countries.put("NO", "Europe/Oslo");
countries.put("NP", "Asia/Katmandu");
countries.put("NZ", "Pacific/Auckland");
countries.put("PDT", "America/Los_Angeles");
countries.put("PH", "Asia/Manila");
countries.put("PK", "Asia/Karachi");
countries.put("PL", "Europe/Warsaw");
countries.put("PST", "America/Los_Angeles");
countries.put("PT", "Europe/Lisbon");
countries.put("PR", "America/Puerto_Rico");
countries.put("RU", "Europe/Moscow");
countries.put("SE", "Europe/Stockholm");
countries.put("SG", "Asia/Singapore");
countries.put("TH", "Asia/Bangkok");
countries.put("TM", "Asia/Ashgabat");
countries.put("TN", "Africa/Tunis");
countries.put("TR", "Europe/Istanbul");
countries.put("TW", "Asia/Taipei");
countries.put("UK", "Europe/London");
countries.put("US", "America/New_York");
countries.put("UTC", "UTC");
countries.put("VA", "Europe/Vatican");
countries.put("VE", "America/Caracas");
countries.put("VN", "Asia/Ho_Chi_Minh");
countries.put("ZA", "Africa/Johannesburg");
countries.put("ZULU", "Zulu");
countries.put("INTERNET", BEATS_KEYWORD);
countries.put("BEATS", BEATS_KEYWORD);
ZoneId.getAvailableZoneIds().stream()
.filter(tz -> !tz.contains("/") && tz.length() == 3 && !countries.containsKey(tz))
.forEach(tz -> countries.put(tz, tz));
COUNTRIES_MAP = Collections.unmodifiableMap(countries);
}
/**
* Creates a new {@link WorldTime} instance.
*/
public WorldTime(final Mobibot bot) {
super(bot);
help.add("To display a country's current date/time:");
help.add(Utils.helpIndent("%c " + TIME_CMD) + " [<country code>]");
help.add("For a listing of the supported countries:");
help.add(Utils.helpIndent("%c " + TIME_CMD));
commands.add(TIME_CMD);
}
/**
* Returns the current Internet (beat) Time.
*
* @return The Internet Time string.
*/
private static String internetTime() {
final ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("UTC+01:00"));
final int beats = (int) ((zdt.get(ChronoField.SECOND_OF_MINUTE) + (zdt.get(ChronoField.MINUTE_OF_HOUR) * 60)
+ (zdt.get(ChronoField.HOUR_OF_DAY) * 3600)) / 86.4);
return String.format("%c%03d", '@', beats);
}
/**
* Returns the world time.
*
* <ul>
* <li>PST</li>
* <li>BEATS</li>
* </ul>
*
* @param query The query.
* @return The {@link Message} containing the world time.
*/
@SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
static Message worldTime(final String query) {
final String tz = (COUNTRIES_MAP.get((StringUtils.upperCase(query.substring(query.indexOf(' ') + 1).trim()))));
final String response;
if (tz != null) {
if (BEATS_KEYWORD.equals(tz)) {
response = ("The current Internet Time is: " + Utils.bold(internetTime() + ' ' + BEATS_KEYWORD));
} else {
response = ZonedDateTime.now().withZoneSameInstant(ZoneId.of(tz)).format(
DateTimeFormatter
.ofPattern("'The time is " + Utils.bold("'HH:mm'") + " on " + Utils.bold(
"'EEEE, d MMMM yyyy'") + " in '"))
+ Utils.bold(tz.substring(tz.indexOf('/') + 1).replace('_', ' '));
}
} else {
return new ErrorMessage("Unsupported country/zone. Please try again.");
}
return new PublicMessage(response);
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (args.length() == 0) {
bot.send(sender, "The supported countries/zones are: ", isPrivate);
bot.sendList(sender, new ArrayList<>(COUNTRIES_MAP.keySet()), 17, false, false);
} else {
final Message msg = worldTime(args);
if (isPrivate) {
bot.send(sender, msg.getMsg(), true);
} else {
if (msg.isError()) {
bot.send(sender, msg.getMsg(), false);
} else {
bot.send(msg.getMsg());
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPrivateMsgEnabled() {
return true;
}
}

View file

@ -0,0 +1,219 @@
/*
* WorldTime.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.PublicMessage
import org.apache.commons.lang3.StringUtils
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
import java.util.*
/**
* The WorldTime module.
*/
class WorldTime(bot: Mobibot) : AbstractModule(bot) {
companion object {
// Beats (Internet Time) keyword
private const val BEATS_KEYWORD = ".beats"
// Supported countries
private var COUNTRIES_MAP: Map<String, String>
// The Time command
private const val TIME_CMD = "time"
/**
* Returns the current Internet (beat) Time.
*/
private fun internetTime(): String {
val zdt = ZonedDateTime.now(ZoneId.of("UTC+01:00"))
val beats = ((zdt[ChronoField.SECOND_OF_MINUTE] + zdt[ChronoField.MINUTE_OF_HOUR] * 60
+ zdt[ChronoField.HOUR_OF_DAY] * 3600) / 86.4).toInt()
return String.format("%c%03d", '@', beats)
}
/**
* Returns the world time.
*/
@JvmStatic
fun worldTime(query: String): Message {
val tz = COUNTRIES_MAP[StringUtils.upperCase(query.substring(query.indexOf(' ') + 1).trim())]
val response: String = if (tz != null) {
if (BEATS_KEYWORD == tz) {
"The current Internet Time is: " + Utils.bold(internetTime() + ' ' + BEATS_KEYWORD)
} else {
(ZonedDateTime.now()
.withZoneSameInstant(ZoneId.of(tz))
.format(
DateTimeFormatter.ofPattern(
"'The time is ${Utils.bold("'HH:mm'")} on ${Utils.bold("'EEEE, d MMMM yyyy'")} in '"
)
)
+ Utils.bold(tz.substring(tz.indexOf('/') + 1).replace('_', ' '))
)
}
} else {
return ErrorMessage("Unsupported country/zone. Please try again.")
}
return PublicMessage(response)
}
init {
// Initialize the countries map
val countries = TreeMap<String, String>()
countries["AE"] = "Asia/Dubai"
countries["AF"] = "Asia/Kabul"
countries["AQ"] = "Antarctica/South_Pole"
countries["AT"] = "Europe/Vienna"
countries["AU"] = "Australia/Sydney"
countries["AKST"] = "America/Anchorage"
countries["AKDT"] = "America/Anchorage"
countries["BE"] = "Europe/Brussels"
countries["BR"] = "America/Sao_Paulo"
countries["CA"] = "America/Montreal"
countries["CDT"] = "America/Chicago"
countries["CET"] = "CET"
countries["CH"] = "Europe/Zurich"
countries["CN"] = "Asia/Shanghai"
countries["CST"] = "America/Chicago"
countries["CU"] = "Cuba"
countries["DE"] = "Europe/Berlin"
countries["DK"] = "Europe/Copenhagen"
countries["EDT"] = "America/New_York"
countries["EG"] = "Africa/Cairo"
countries["ER"] = "Africa/Asmara"
countries["ES"] = "Europe/Madrid"
countries["EST"] = "America/New_York"
countries["FI"] = "Europe/Helsinki"
countries["FR"] = "Europe/Paris"
countries["GB"] = "Europe/London"
countries["GMT"] = "GMT"
countries["GR"] = "Europe/Athens"
countries["HK"] = "Asia/Hong_Kong"
countries["HST"] = "Pacific/Honolulu"
countries["IE"] = "Europe/Dublin"
countries["IL"] = "Asia/Tel_Aviv"
countries["IN"] = "Asia/Kolkata"
countries["IQ"] = "Asia/Baghdad"
countries["IR"] = "Asia/Tehran"
countries["IS"] = "Atlantic/Reykjavik"
countries["IT"] = "Europe/Rome"
countries["JM"] = "Jamaica"
countries["JP"] = "Asia/Tokyo"
countries["LY"] = "Africa/Tripoli"
countries["MA"] = "Africa/Casablanca"
countries["MDT"] = "America/Denver"
countries["MH"] = "Kwajalein"
countries["MQ"] = "America/Martinique"
countries["MST"] = "America/Denver"
countries["MX"] = "America/Mexico_City"
countries["NL"] = "Europe/Amsterdam"
countries["NO"] = "Europe/Oslo"
countries["NP"] = "Asia/Katmandu"
countries["NZ"] = "Pacific/Auckland"
countries["PDT"] = "America/Los_Angeles"
countries["PH"] = "Asia/Manila"
countries["PK"] = "Asia/Karachi"
countries["PL"] = "Europe/Warsaw"
countries["PST"] = "America/Los_Angeles"
countries["PT"] = "Europe/Lisbon"
countries["PR"] = "America/Puerto_Rico"
countries["RU"] = "Europe/Moscow"
countries["SE"] = "Europe/Stockholm"
countries["SG"] = "Asia/Singapore"
countries["TH"] = "Asia/Bangkok"
countries["TM"] = "Asia/Ashgabat"
countries["TN"] = "Africa/Tunis"
countries["TR"] = "Europe/Istanbul"
countries["TW"] = "Asia/Taipei"
countries["UK"] = "Europe/London"
countries["US"] = "America/New_York"
countries["UTC"] = "UTC"
countries["VA"] = "Europe/Vatican"
countries["VE"] = "America/Caracas"
countries["VN"] = "Asia/Ho_Chi_Minh"
countries["ZA"] = "Africa/Johannesburg"
countries["ZULU"] = "Zulu"
countries["INTERNET"] = BEATS_KEYWORD
countries["BEATS"] = BEATS_KEYWORD
ZoneId.getAvailableZoneIds().stream()
.filter { tz: String ->
!tz.contains("/") && tz.length == 3 && !countries.containsKey(tz)
}
.forEach { tz: String ->
countries[tz] = tz
}
COUNTRIES_MAP = Collections.unmodifiableMap(countries)
}
}
override fun commandResponse(
sender: String,
cmd: String,
args: String,
isPrivate: Boolean
) {
with(bot) {
if (args.isEmpty()) {
send(sender, "The supported countries/zones are: ", isPrivate)
sendList(sender, ArrayList(COUNTRIES_MAP.keys), 17, false, false)
} else {
val msg = worldTime(args)
if (isPrivate) {
send(sender, msg.msg, true)
} else {
if (msg.isError) {
send(sender, msg.msg, false)
} else {
send(msg.msg)
}
}
}
}
}
override val isPrivateMsgEnabled = true
init {
help.add("To display a country's current date/time:")
help.add(Utils.helpIndent("%c $TIME_CMD") + " [<country code>]")
help.add("For a listing of the supported countries:")
help.add(Utils.helpIndent("%c $TIME_CMD"))
commands.add(TIME_CMD)
}
}

View file

@ -1,5 +1,5 @@
/*
* ErrorMessage.java
* ErrorMessage.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -33,10 +33,6 @@ package net.thauvin.erik.mobibot.msg
/**
* The `ErrorMessage` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-07
* @since 1.0
*/
class ErrorMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) : Message() {
init {

View file

@ -35,10 +35,6 @@ import org.jibble.pircbot.Colors
/**
* The `Message` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-07
* @since 1.0
*/
open class Message {
companion object {
@ -67,12 +63,6 @@ open class Message {
/**
* Creates a new message.
*
* @param msg The message.
* @param color The color.
* @param isNotice The notice flag.
* @param isError The error flag.
* @param isPrivate The private flag.
*/
@JvmOverloads
constructor(msg: String, color: String = DEFAULT_COLOR, isNotice: Boolean, isError: Boolean, isPrivate: Boolean) {

View file

@ -1,5 +1,5 @@
/*
* NoticeMessage.java
* NoticeMessage.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -33,10 +33,6 @@ package net.thauvin.erik.mobibot.msg
/**
* The `NoticeMessage` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-07
* @since 1.0
*/
class NoticeMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) : Message() {
init {

View file

@ -1,5 +1,5 @@
/*
* PrivateMessage.java
* PrivateMessage.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -33,10 +33,6 @@ package net.thauvin.erik.mobibot.msg
/**
* The `PrivateMessage` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-09
* @since 1.0
*/
@Suppress("unused")
class PrivateMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) : Message() {

View file

@ -1,5 +1,5 @@
/*
* PublicMessage.java
* PublicMessage.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -33,10 +33,6 @@ package net.thauvin.erik.mobibot.msg
/**
* The `PublicMessage` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-07
* @since 1.0
*/
class PublicMessage @JvmOverloads constructor(msg: String, color: String = DEFAULT_COLOR) : Message() {
init {

View file

@ -66,7 +66,7 @@ public class UtilsTest {
@Test
public void testBold() {
assertThat(Utils.bold(1)).as("bold(1)").isEqualTo(Colors.BOLD + "1" + Colors.BOLD);
assertThat(Utils.bold(Integer.toString(1))).as("bold(1)").isEqualTo(Colors.BOLD + "1" + Colors.BOLD);
assertThat(Utils.bold(ASCII)).as("bold(ascii").isEqualTo(Colors.BOLD + ASCII + Colors.BOLD);
}

View file

@ -1,5 +1,5 @@
/*
* CalcTest.java
* CalcTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,32 +29,26 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Utils;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.modules.Calc.Companion.calc
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The <code>CalcTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
* The `CalcTest` class.
*/
public class CalcTest {
class CalcTest {
@Test
public void testCalc() {
assertThat(Calc.calc("1 + 1")).as("calc(1+1)").isEqualTo("1+1 = %s", Utils.bold(2));
assertThat(Calc.calc("1 -3")).as("calc(1 -3)").isEqualTo("1-3 = %s", Utils.bold(-2));
assertThat(Calc.calc("pi+π+e+φ")).as("calc(pi+π+e+φ)").isEqualTo("pi+π+e+φ = %s", Utils.bold("10.62"));
assertThat(Calc.calc("one + one")).as("calc(one + one)").startsWith("No idea.");
}
@Test
public void testCalcImpl() {
AbstractModuleTest.testAbstractModule(new Calc(null));
fun testCalc() {
Assertions.assertThat(calc("1 + 1")).`as`("calc(1+1)")
.isEqualTo("1+1 = %s", Utils.bold(2))
Assertions.assertThat(calc("1 -3")).`as`("calc(1 -3)")
.isEqualTo("1-3 = %s", Utils.bold(-2))
Assertions.assertThat(calc("pi+π+e+φ")).`as`("calc(pi+π+e+φ)")
.isEqualTo("pi+π+e+φ = %s", Utils.bold("10.62"))
Assertions.assertThat(calc("one + one")).`as`("calc(one + one)")
.startsWith("No idea.")
}
}

View file

@ -1,73 +0,0 @@
/*
* CurrencyConverterTest.java
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The <code>CurrencyConvertTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class CurrencyConverterTest {
@BeforeClass
public void before() throws ModuleException {
CurrencyConverter.loadRates();
}
@Test
@SuppressFBWarnings("PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS")
public void testConvertCurrency() {
assertThat(CurrencyConverter.convertCurrency("100 USD to EUR").getMsg())
.as("100 USD to EUR").matches("100\\.00 USD = \\d{2,3}\\.\\d{2} EUR");
assertThat(CurrencyConverter.convertCurrency("100 USD to USD").getMsg())
.as("100 USD to USD").contains("You're kidding, right?");
assertThat(CurrencyConverter.convertCurrency("100 USD").getMsg())
.as("100 USD").contains("Invalid query.");
assertThat(CurrencyConverter.currencyRates().size())
.as("currencyRates().size() == 33").isEqualTo(33);
assertThat(CurrencyConverter.currencyRates())
.as("currencyRates().get(EUR)").contains(" EUR: 1");
}
@Test
public void testCurrencyConvertererImpl() {
AbstractModuleTest.testAbstractModule(new CurrencyConverter(null));
}
}

View file

@ -1,5 +1,5 @@
/*
* LookupTest.java
* CurrencyConverterTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,40 +29,36 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import org.testng.annotations.Test;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.convertCurrency
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.currencyRates
import net.thauvin.erik.mobibot.modules.CurrencyConverter.Companion.loadRates
import org.assertj.core.api.Assertions
import org.testng.annotations.BeforeClass
import org.testng.annotations.Test
/**
* The <code>Lookup Test</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2017-05-30
* @since 1.0
* The `CurrencyConvertTest` class.
*/
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public class LookupTest {
@Test
public void testLookupImpl() {
AbstractModuleTest.testAbstractModule(new Lookup(null));
class CurrencyConverterTest {
@BeforeClass
@Throws(ModuleException::class)
fun before() {
loadRates()
}
@Test
public void testLookup() throws Exception {
final String result = Lookup.lookup("erik.thauvin.net");
assertThat(result).as("lookup(erik.thauvin.net/104.31.77.12)").contains("104.31.77.12");
}
@Test
public void testWhois() throws Exception {
final String[] result = Lookup.whois("17.178.96.59", Lookup.WHOIS_HOST);
assertThat(Arrays.stream(result).anyMatch(m -> m.contains("Apple Inc.")))
.as("whois(17.178.96.59/Apple Inc.").isTrue();
fun testConvertCurrency() {
Assertions.assertThat(convertCurrency("100 USD to EUR").msg)
.`as`("100 USD to EUR").matches("100\\.00 USD = \\d{2,3}\\.\\d{2} EUR")
Assertions.assertThat(convertCurrency("100 USD to USD").msg)
.`as`("100 USD to USD").contains("You're kidding, right?")
Assertions.assertThat(convertCurrency("100 USD").msg)
.`as`("100 USD").contains("Invalid query.")
Assertions.assertThat(currencyRates().size)
.`as`("currencyRates().size() == 33").isEqualTo(33)
Assertions.assertThat(currencyRates())
.`as`("currencyRates().get(EUR)").contains(" EUR: 1")
}
}

View file

@ -0,0 +1,52 @@
/*
* DiceTest.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import org.assertj.core.api.Assertions.assertThat
import org.testng.annotations.Test
class DiceTest {
@Test
fun testWinLoseOrTie() {
assertThat(
Dice.winLoseOrTie(6, 6)
).`as`("6 vs. 6").isEqualTo(Dice.Result.TIE)
assertThat(
Dice.winLoseOrTie(6, 5)
).`as`("6 vs. 5").isEqualTo(Dice.Result.WIN)
assertThat(
Dice.winLoseOrTie(5, 6)
).`as`("5 vs. 6").isEqualTo(Dice.Result.LOSE)
}
}

View file

@ -1,90 +0,0 @@
/*
* GoogleSearchTest.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.LocalProperties;
import net.thauvin.erik.mobibot.msg.Message;
import org.testng.annotations.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* The <code>GoogleSearchTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-08
* @since 1.0
*/
public class GoogleSearchTest extends LocalProperties {
@Test
public void testGoogleSearchImpl() {
AbstractModuleTest.testAbstractModule(new GoogleSearch(null));
}
@SuppressFBWarnings("LEST_LOST_EXCEPTION_STACK_TRACE")
@SuppressWarnings("PMD.PreserveStackTrace")
@Test
public void testSearchGoogle() throws ModuleException {
final String apiKey = getProperty(GoogleSearch.GOOGLE_API_KEY_PROP);
final String cseKey = getProperty(GoogleSearch.GOOGLE_CSE_KEY_PROP);
try {
List<Message> messages = GoogleSearch.searchGoogle("mobibot site:github.com", apiKey, cseKey);
assertThat(messages).as("mobibot results not empty").isNotEmpty();
assertThat(messages.get(0).getMsg()).as("found mobitopia").contains("mobibot");
messages = GoogleSearch.searchGoogle("aapl", apiKey, cseKey);
assertThat(messages).as("aapl results not empty").isNotEmpty();
assertThat(messages.get(0).getMsg()).as("found apple").containsIgnoringCase("apple");
assertThatThrownBy(() -> GoogleSearch.searchGoogle("test", "", "apiKey")).as("no API key").isInstanceOf(
ModuleException.class).hasNoCause();
assertThatThrownBy(() -> GoogleSearch.searchGoogle("test", "apiKey", "")).as("no CSE API key").isInstanceOf(
ModuleException.class).hasNoCause();
assertThatThrownBy(() -> GoogleSearch.searchGoogle("", "apikey", "apiKey")).as("no query").isInstanceOf(
ModuleException.class).hasNoCause();
} catch (ModuleException e) {
// Avoid displaying api keys in CI logs
if ("true".equals(System.getenv("CI"))) {
throw new ModuleException(e.getDebugMessage(), e.getSanitizedMessage(apiKey, cseKey));
} else {
throw e;
}
}
}
}

View file

@ -0,0 +1,72 @@
/*
* GoogleSearchTest.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.GoogleSearch.Companion.searchGoogle
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The `GoogleSearchTest` class.
*/
class GoogleSearchTest : LocalProperties() {
@Test
@Throws(ModuleException::class)
fun testSearchGoogle() {
val apiKey = getProperty(GoogleSearch.GOOGLE_API_KEY_PROP)
val cseKey = getProperty(GoogleSearch.GOOGLE_CSE_KEY_PROP)
try {
var messages = searchGoogle("mobibot site:github.com", apiKey, cseKey)
Assertions.assertThat(messages).`as`("mobibot results not empty").isNotEmpty
Assertions.assertThat(messages[0].msg).`as`("found mobitopia").contains("mobibot")
messages = searchGoogle("aapl", apiKey, cseKey)
Assertions.assertThat(messages).`as`("aapl results not empty").isNotEmpty
Assertions.assertThat(messages[0].msg).`as`("found apple").containsIgnoringCase("apple")
Assertions.assertThatThrownBy { searchGoogle("test", "", "apiKey") }
.`as`("no API key")
.isInstanceOf(ModuleException::class.java).hasNoCause()
Assertions.assertThatThrownBy { searchGoogle("test", "apiKey", "") }
.`as`("no CSE API key")
.isInstanceOf(ModuleException::class.java).hasNoCause()
Assertions.assertThatThrownBy { searchGoogle("", "apikey", "apiKey") }
.`as`("no query").isInstanceOf(ModuleException::class.java).hasNoCause()
} catch (e: ModuleException) {
// Avoid displaying api keys in CI logs
if ("true" == System.getenv("CI")) {
throw ModuleException(e.debugMessage, e.getSanitizedMessage(apiKey, cseKey))
} else {
throw e
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* JokeTest.java
* JokeTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,29 +29,20 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.modules.Joke.Companion.randomJoke
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The <code>JokeTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
* The `JokeTest` class.
*/
public class JokeTest {
class JokeTest {
@Test
public void testJokeImpl() {
AbstractModuleTest.testAbstractModule(new Joke(null));
}
@Test
public void testRamdomJoke() throws ModuleException {
assertThat(Joke.randomJoke().getMsg().length() > 0).as("randomJoke() > 0").isTrue();
assertThat(Joke.randomJoke().getMsg()).as("randomJoke()").containsIgnoringCase("chuck");
@Throws(ModuleException::class)
fun testRamdomJoke() {
Assertions.assertThat(randomJoke().msg.isNotEmpty()).`as`("randomJoke() > 0").isTrue
Assertions.assertThat(randomJoke().msg).`as`("randomJoke()").containsIgnoringCase("chuck")
}
}

View file

@ -1,5 +1,5 @@
/*
* WordTimeTest.java
* LookupTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,31 +29,30 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Utils;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.modules.Lookup.Companion.lookup
import net.thauvin.erik.mobibot.modules.Lookup.Companion.whois
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
import java.util.*
/**
* The <code>WordTimeTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
* The `Lookup Test` class.
*/
public class WordTimeTest {
class LookupTest {
@Test
public void testWorldTime() {
assertThat(WorldTime.worldTime("PST").getMsg()).as("PST").endsWith(Utils.bold("Los Angeles"));
assertThat(WorldTime.worldTime("BLAH").isError()).as("BLAH").isTrue();
assertThat(WorldTime.worldTime("BEATS").getMsg()).as("BEATS").contains("@");
@Throws(Exception::class)
fun testLookup() {
val result = lookup("erik.thauvin.net")
Assertions.assertThat(result).`as`("lookup(erik.thauvin.net/104.31.77.12)").contains("104.31.77.12")
}
@Test
public void testWorldTimeImpl() {
AbstractModuleTest.testAbstractModule(new Lookup(null));
@Throws(Exception::class)
fun testWhois() {
val result = whois("17.178.96.59", Lookup.WHOIS_HOST)
Assertions.assertThat(Arrays.stream(result).anyMatch { m: String -> m.contains("Apple Inc.") })
.`as`("whois(17.178.96.59/Apple Inc.").isTrue
}
}

View file

@ -42,10 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* The <code>ModuleExceptionTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-09
* @since 1.0
*/
public class ModuleExceptionTest {
static final String debugMessage = "debugMessage";
@ -53,13 +49,13 @@ public class ModuleExceptionTest {
@DataProvider(name = "dp")
Object[][] createData(final Method m) {
return new Object[][]{new Object[]{new ModuleException(debugMessage,
message,
new IOException("URL http://foobar.com"))},
new Object[]{new ModuleException(debugMessage,
message,
new IOException("URL http://foobar.com?"))},
new Object[]{new ModuleException(debugMessage, message)}};
return new Object[][]{ new Object[]{ new ModuleException(debugMessage,
message,
new IOException("URL http://foobar.com")) },
new Object[]{ new ModuleException(debugMessage,
message,
new IOException("URL http://foobar.com?")) },
new Object[]{ new ModuleException(debugMessage, message) } };
}
@Test(dataProvider = "dp")
@ -78,7 +74,7 @@ public class ModuleExceptionTest {
final ModuleException e = new ModuleException(debugMessage,
message,
new IOException(
"URL http://foo.com?apiKey=" + apiKey + "&userID=me"));
"URL http://foo.com?apiKey=" + apiKey + "&userID=me"));
assertThat(e.getSanitizedMessage(apiKey)).as("sanitized url").contains("xxxxxxxxxx").doesNotContain(apiKey);
}
}

View file

@ -1,5 +1,5 @@
/*
* PingTest.java
* PingTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,28 +29,25 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.modules.Ping.Companion.randomPing
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The <code>PingTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
* The `PingTest` class.
*/
public class PingTest {
class PingTest {
@Test
public void testPingImpl() {
AbstractModuleTest.testAbstractModule(new Ping(null));
fun testPingsArray() {
Assertions.assertThat(Ping.PINGS).`as`("Pings array is not empty.").isNotEmpty
}
@Test
public void testPingsArray() {
assertThat(Ping.PINGS).as("Pings array is not empty.").isNotEmpty();
fun testRandomPing() {
for (i in 0..9) {
Assertions.assertThat(randomPing()).`as`("Random ping $i").isIn(Ping.PINGS)
}
}
}

View file

@ -32,7 +32,6 @@
package net.thauvin.erik.mobibot.modules
import org.assertj.core.api.Assertions.assertThat
import org.testng.annotations.Test

View file

@ -1,90 +0,0 @@
/*
* StockQuoteTest.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.LocalProperties;
import net.thauvin.erik.mobibot.msg.Message;
import org.testng.annotations.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* The <code>StockQuoteTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class StockQuoteTest extends LocalProperties {
@SuppressFBWarnings("LEST_LOST_EXCEPTION_STACK_TRACE")
@SuppressWarnings("PMD.PreserveStackTrace")
@Test
public void testGetQuote() throws ModuleException {
final String apiKey = LocalProperties.getProperty(StockQuote.ALPHAVANTAGE_API_KEY_PROP);
try {
final List<Message> messages = StockQuote.getQuote("apple inc", apiKey);
assertThat(messages).as("response not empty").isNotEmpty();
assertThat(messages.get(0).getMsg()).as("same stock symbol").contains("AAPL").contains("Apple Inc.");
try {
StockQuote.getQuote("blahfoo", apiKey);
} catch (ModuleException e) {
assertThat(e.getMessage()).as("invalid symbol").containsIgnoringCase(StockQuote.INVALID_SYMBOL);
}
assertThatThrownBy(() -> StockQuote.getQuote("test", "")).as("no API key").isInstanceOf(
ModuleException.class).hasNoCause();
assertThatThrownBy(() -> StockQuote.getQuote("", "apikey")).as("no symbol").isInstanceOf(
ModuleException.class).hasNoCause();
} catch (ModuleException e) {
// Avoid displaying api keys in CI logs
if ("true".equals(System.getenv("CI"))) {
throw new ModuleException(e.getDebugMessage(), e.getSanitizedMessage(apiKey));
} else {
throw e;
}
}
}
@Test
public void testStockQuoteImpl() {
AbstractModuleTest.testAbstractModule(new StockQuote(null));
}
}

View file

@ -0,0 +1,73 @@
/*
* StockQuoteTest.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.StockQuote.Companion.getQuote
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The `StockQuoteTest` class.
*/
class StockQuoteTest : LocalProperties() {
@Test
@Throws(ModuleException::class)
fun testGetQuote() {
val apiKey = getProperty(StockQuote.ALPHAVANTAGE_API_KEY_PROP)
try {
val messages = getQuote("apple inc", apiKey)
Assertions.assertThat(messages).`as`("response not empty").isNotEmpty
Assertions.assertThat(messages[0].msg).`as`("same stock symbol")
.isEqualTo("Symbol: AAPL [Apple Inc.]")
Assertions.assertThat(messages[1].msg).`as`("price label")
.startsWith(" Price: ")
try {
getQuote("blahfoo", apiKey)
} catch (e: ModuleException) {
Assertions.assertThat(e.message).`as`("invalid symbol")
.containsIgnoringCase(StockQuote.INVALID_SYMBOL)
}
Assertions.assertThatThrownBy { getQuote("test", "") }.`as`("no API key")
.isInstanceOf(ModuleException::class.java).hasNoCause()
Assertions.assertThatThrownBy { getQuote("", "apikey") }.`as`("no symbol")
.isInstanceOf(ModuleException::class.java).hasNoCause()
} catch (e: ModuleException) {
// Avoid displaying api keys in CI logs
if ("true" == System.getenv("CI")) {
throw ModuleException(e.debugMessage, e.getSanitizedMessage(apiKey))
} else {
throw e
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* TwitterTest.java
* TwitterTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,50 +29,43 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.LocalProperties;
import org.testng.annotations.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.assertj.core.api.Assertions.assertThat;
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.Twitter.Companion.twitterPost
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
import java.net.InetAddress
import java.net.UnknownHostException
/**
* The <code>TwitterTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-19
* @since 1.0
* The `TwitterTest` class.
*/
public class TwitterTest extends LocalProperties {
@SuppressFBWarnings("MDM")
private String getCi() {
final String ciName = System.getenv("CI_NAME");
if (ciName != null) {
return ciName;
} else {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "Unknown Host";
class TwitterTest : LocalProperties() {
private val ci: String
get() {
val ciName = System.getenv("CI_NAME")
return ciName ?: try {
InetAddress.getLocalHost().hostName
} catch (e: UnknownHostException) {
"Unknown Host"
}
}
}
@Test
public void testPostTwitter() throws ModuleException {
final String msg = "Testing Twitter API from " + getCi();
assertThat(Twitter.twitterPost(
@Throws(ModuleException::class)
fun testPostTwitter() {
val msg = "Testing Twitter API from $ci"
Assertions.assertThat(
twitterPost(
getProperty(Twitter.CONSUMER_KEY_PROP),
getProperty(Twitter.CONSUMER_SECRET_PROP),
getProperty(Twitter.TOKEN_PROP),
getProperty(Twitter.TOKEN_SECRET_PROP),
getProperty(Twitter.HANDLE_PROP),
msg,
true).getMsg()).as("twitterPost(" + msg + ')').isEqualTo(msg);
true
).msg
).`as`("twitterPost($msg)").isEqualTo(msg)
}
}

View file

@ -1,80 +0,0 @@
/*
* Weather2Test.java
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.aksingh.owmjapis.api.APIException;
import net.thauvin.erik.mobibot.LocalProperties;
import net.thauvin.erik.mobibot.msg.Message;
import org.testng.annotations.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* The <code>Weather2Test</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-09
* @since 1.0
*/
public class Weather2Test extends LocalProperties {
@SuppressFBWarnings("PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS")
@Test
public void testWeather() throws ModuleException {
List<Message> messages = Weather2.getWeather("98204", getProperty(Weather2.OWM_API_KEY_PROP));
assertThat(messages.get(0).getMsg()).as("is Everett").contains("Everett").contains("US");
assertThat(messages.get(messages.size() - 1).getMsg()).as("is City Search").endsWith("98204%2CUS");
messages = Weather2.getWeather("London, UK", getProperty(Weather2.OWM_API_KEY_PROP));
assertThat(messages.get(0).getMsg()).as("is UK").contains("London").contains("UK");
assertThat(messages.get(messages.size() - 1).getMsg()).as("is City Code").endsWith("4517009");
assertThatThrownBy(() -> Weather2.getWeather("Montpellier, FR", getProperty(Weather2.OWM_API_KEY_PROP)))
.as("Montpellier not found").hasCauseInstanceOf(APIException.class);
assertThatThrownBy(() -> Weather2.getWeather("test", ""))
.as("no API key").isInstanceOf(ModuleException.class).hasNoCause();
messages = Weather2.getWeather("", "apikey");
assertThat(messages.get(0).isError()).as("no query").isTrue();
}
@Test
public void testWeather2Impl() {
AbstractModuleTest.testAbstractModule(new Weather2(null));
}
}

View file

@ -0,0 +1,60 @@
/*
* Weather2Test.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.aksingh.owmjapis.api.APIException
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.Weather2.Companion.getWeather
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The `Weather2Test` class.
*/
class Weather2Test : LocalProperties() {
@Test
@Throws(ModuleException::class)
fun testWeather() {
var messages = getWeather("98204", getProperty(Weather2.OWM_API_KEY_PROP))
Assertions.assertThat(messages[0].msg).`as`("is Everett").contains("Everett").contains("US")
Assertions.assertThat(messages[messages.size - 1].msg).`as`("is City Search").endsWith("98204%2CUS")
messages = getWeather("London, UK", getProperty(Weather2.OWM_API_KEY_PROP))
Assertions.assertThat(messages[0].msg).`as`("is UK").contains("London").contains("UK")
Assertions.assertThat(messages[messages.size - 1].msg).`as`("is City Code").endsWith("4517009")
Assertions.assertThatThrownBy { getWeather("Montpellier, FR", getProperty(Weather2.OWM_API_KEY_PROP)) }
.`as`("Montpellier not found").hasCauseInstanceOf(APIException::class.java)
Assertions.assertThatThrownBy { getWeather("test", "") }
.`as`("no API key").isInstanceOf(ModuleException::class.java).hasNoCause()
messages = getWeather("", "apikey")
Assertions.assertThat(messages[0].isError).`as`("no query").isTrue
}
}

View file

@ -0,0 +1,49 @@
/*
* WordTimeTest.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.modules.WorldTime.Companion.worldTime
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
/**
* The `WordTimeTest` class.
*/
class WordTimeTest {
@Test
fun testWorldTime() {
Assertions.assertThat(worldTime("PST").msg).`as`("PST").endsWith(Utils.bold("Los Angeles"))
Assertions.assertThat(worldTime("BLAH").isError).`as`("BLAH").isTrue
Assertions.assertThat(worldTime("BEATS").msg).`as`("BEATS").contains("@")
}
}