Merge branch '0.8'

This commit is contained in:
Erik C. Thauvin 2022-01-19 15:31:08 -08:00
commit 8c13c44731
170 changed files with 9770 additions and 9289 deletions

View file

@ -4,7 +4,7 @@ defaults: &defaults
environment:
JVM_OPTS: -Xmx3200m
TERM: dumb
CI: true
CI_NAME: "CircleCI"
defaults_gradle: &defaults_gradle
steps:
@ -18,7 +18,8 @@ defaults_gradle: &defaults_gradle
name: Gradle Dependencies
command: ./gradlew dependencies
- save_cache:
paths: ~/.m2
paths:
- ~/.m2
key: gradle-dependencies-{{ checksum "build.gradle" }}
- run:
name: Run All Checks
@ -30,26 +31,25 @@ defaults_gradle: &defaults_gradle
path: build/reports/
jobs:
build_gradle_jdk12:
build_gradle_jdk17:
<<: *defaults
docker:
- image: openjdk:12-jdk
- image: cimg/openjdk:17.0
<<: *defaults_gradle
build_gradle_jdk8:
build_gradle_jdk11:
<<: *defaults
docker:
- image: circleci/openjdk:8-jdk
- image: cimg/openjdk:11.0
<<: *defaults_gradle
workflows:
version: 2
gradle:
jobs:
- build_gradle_jdk8
- build_gradle_jdk12
jobs:
- build_gradle_jdk11
- build_gradle_jdk17

View file

@ -1,2 +1,2 @@
[*]
insert_final_newline=true
insert_final_newline = true

73
.github/workflows/gradle.yml vendored Normal file
View file

@ -0,0 +1,73 @@
name: gradle-ci
on: [ push, pull_request, workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=512m"
SONAR_JDK: "11"
strategy:
matrix:
java-version: [ 11, 17 ]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java-version }}
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Cache SonarCloud packages
if: matrix.java-version == env.SONAR_JDK
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ matrix.java-version }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-${{ matrix.java-version }}-
- name: Test with Gradle
env:
CI_NAME: "GitHub CI"
ALPHAVANTAGE_API_KEY: ${{ secrets.ALPHAVANTAGE_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GOOGLE_CSE_CX: ${{ secrets.GOOGLE_CSE_CX }}
OWM_API_KEY: ${{ secrets.OWM_API_KEY }}
PINBOARD_API_TOKEN: ${{ secrets.PINBOARD_API_TOKEN }}
TWITTER_CONSUMERKEY: ${{ secrets.TWITTER_CONSUMERKEY }}
TWITTER_CONSUMERSECRET: ${{ secrets.TWITTER_CONSUMERSECRET }}
TWITTER_HANDLE: ${{ secrets.TWITTER_HANDLE }}
TWITTER_TOKEN: ${{ secrets.TWITTER_TOKEN }}
TWITTER_TOKENSECRET: ${{ secrets.TWITTER_TOKENSECRET }}
run: ./gradlew build check --stacktrace
- name: SonarCloud
if: success() && matrix.java-version == env.SONAR_JDK
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew sonarqube
- name: Cleanup Gradle Cache
run: |
rm -f ~/.gradle/caches/modules-2/modules-2.lock
rm -f ~/.gradle/caches/modules-2/gc.properties

73
.gitignore vendored
View file

@ -1,44 +1,9 @@
__pycache__
!.vscode/extensions.json
!.vscode/launch.json
!.vscode/settings.json
!.vscode/tasks.json
!gradle-wrapper.jar
!properties/*
.classpath
.DS_Store
.gradle
.history
.idea_modules/
.idea/**/contentModel.xml
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/dataSources/
.idea/**/dbnavigator.xml
.idea/**/dictionaries
.idea/**/dynamic.xml
.idea/**/gradle.xml
.idea/**/libraries
.idea/**/mongoSettings.xml
.idea/**/shelf
.idea/**/sqlDataSources.xml
.idea/**/tasks.xml
.idea/**/uiDesigner.xml
.idea/**/usage.statistics.xml
.idea/**/workspace.xml
.idea/**/caches/build_file_checksums.ser
.idea/**/httpRequests
.idea/**/replstate.xml
.idea/**/shelf/
.kobalt
.mtj.tmp/
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.nb-gradle
.project
.scannerwork
.settings
.vscode/*
*.class
*.code-workspace
*.ctxt
@ -52,6 +17,43 @@ __pycache__
*.tar.gz
*.war
*.zip
.DS_Store
.classpath
.gradle
.history
.idea/**/caches/build_file_checksums.ser
.idea/**/contentModel.xml
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/dataSources/
.idea/**/dbnavigator.xml
.idea/**/dictionaries
.idea/**/dynamic.xml
.idea/**/gradle.xml
.idea/**/httpRequests
.idea/**/libraries
.idea/**/mongoSettings.xml
.idea/**/replstate.xml
.idea/**/shelf
.idea/**/shelf/
.idea/**/sonarlint*
.idea/**/sqlDataSources.xml
.idea/**/tasks.xml
.idea/**/uiDesigner.xml
.idea/**/usage.statistics.xml
.idea/**/workspace.xml
.idea_modules/
.kobalt
.mtj.tmp/
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.nb-gradle
.project
.scannerwork
.settings
.vscode/*
Thumbs.db
__pycache__
atlassian-ide-plugin.xml
bin/
build/
@ -85,5 +87,4 @@ project.properties
release.properties
target/
test-output
Thumbs.db
venv

32
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,32 @@
image: gradle:7-jdk17
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
CI_NAME: "GitLab CI"
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
stages:
- build
- test
build:
stage: build
script: gradle --build-cache assemble
cache:
key: "$CI_COMMIT_REF_NAME"
policy: push
paths:
- build
- .gradle
test:
stage: test
script: gradle check
cache:
key: "$CI_COMMIT_REF_NAME"
policy: pull
paths:
- build
- .gradle

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CheckStyle-IDEA">
<option name="configuration">
<map>
<entry key="checkstyle-version" value="8.29" />
<entry key="copy-libs" value="true" />
<entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
<entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
<entry key="scan-before-checkin" value="false" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
</map>
</option>
</component>
</project>

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

@ -0,0 +1,15 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<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="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View file

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

26
.idea/compiler.xml generated
View file

@ -1,30 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<option name="semver.project.dir" value="K:\java\mobibot" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/net/thauvin/erik/semver/1.2.0/semver-1.2.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/github/spullara/mustache/java/compiler/0.9.6/compiler-0.9.6.jar" />
</processorPath>
<module name="mobibot.main" />
</profile>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<option name="semver.project.dir" value="K:\java\mobibot" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/net/thauvin/erik/semver/1.2.0/semver-1.2.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/github/spullara/mustache/java/compiler/0.9.6/compiler-0.9.6.jar" />
</processorPath>
<module name="mobibot.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="mobibot" target="14" />
<module name="mobibot.main" target="14" />
<module name="mobibot.test" target="14" />
</bytecodeTargetLevel>
<bytecodeTargetLevel target="11" />
</component>
</project>

View file

@ -30,7 +30,7 @@
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="created" />
<option name="myAdditionalJavadocTags" value="created,created,created" />
</inspection_tool>
</profile>
</component>

View file

@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Erik's Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View file

@ -11,20 +11,25 @@
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:$MAVEN_REPOSITORY$/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$MAVEN_REPOSITORY$/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>

11
.idea/misc.xml generated
View file

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
<component name="EntryPointsManager">
<pattern value="net.thauvin.erik.mobibot.modules.War" />
<pattern value="net.thauvin.erik.mobibot.modules.War" method="War" />
</component>
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="14" project-jdk-type="JavaSDK" />
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="17" project-jdk-type="JavaSDK" />
</project>

12
.idea/mobibot.iml generated
View file

@ -1,12 +0,0 @@
<?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.7.3-beta+739" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_14" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

10
.idea/modules.xml generated
View file

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

View file

@ -1,56 +0,0 @@
<?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.7.3-beta+547" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager">
<output url="file://$MODULE_DIR$/../../out/production/classes" />
<output-test url="file://$MODULE_DIR$/../../out/test/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$/../..">
<sourceFolder url="file://$MODULE_DIR$/../../src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../src/generated/java" isTestSource="false" generated="true" />
<excludeFolder url="file://$MODULE_DIR$/../../.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../build" />
<excludeFolder url="file://$MODULE_DIR$/../../kobaltBuild" />
<excludeFolder url="file://$MODULE_DIR$/../../out" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: pircbot:pircbot:1.5.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: pircbot:pircbot:1.5.0:sources" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-core:2.12.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-slf4j-impl:2.12.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-api:2.12.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.commons:commons-lang3:3.9" 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.6" level="project" />
<orderEntry type="library" name="Gradle: net.thauvin.erik:pinboard-poster:1.0.1" level="project" />
<orderEntry type="library" name="Gradle: net.aksingh:owm-japis:2.5.3.0" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.retrofit2:converter-gson:2.5.0" 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:4.2.0" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome:1.12.2" level="project" />
<orderEntry type="library" name="Gradle: org.json:json:20190722" level="project" />
<orderEntry type="library" name="Gradle: org.jsoup:jsoup:1.12.1" level="project" />
<orderEntry type="library" name="Gradle: net.objecthunter:exp4j:0.4.8" level="project" />
<orderEntry type="library" name="Gradle: org.twitter4j:twitter4j-core:4.0.7" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: net.thauvin.erik:semver:1.2.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.github.spotbugs:spotbugs-annotations:4.0.0-beta4" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome-utils:1.12.2" level="project" />
<orderEntry type="library" name="Gradle: org.slf4j:slf4j-api:1.7.25" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okio:okio:2.2.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.3.50" level="project" />
<orderEntry type="library" name="Gradle: org.jdom:jdom2:2.0.6" level="project" />
<orderEntry type="library" name="Gradle: com.google.code.gson:gson:2.8.5" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.github.spullara.mustache.java:compiler:0.9.6" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.testng:testng:7.0.0" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.assertj:assertj-core:3.13.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: com.beust:jcommander:1.72" level="project" />
</component>
</module>

View file

@ -1,82 +0,0 @@
<?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.7.3-beta+739" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JVM 1.6" allPlatforms="JVM [1.6]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xallow-no-source-files" />
</compilerSettings>
<compilerArguments>
<option name="destination" value="$MODULE_DIR$/../../build/classes/kotlin/main" />
<option name="classpath" value="$MAVEN_REPOSITORY$/net/thauvin/erik/semver/1.2.0/semver-1.2.0.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/cc27715d1c9c8246beb6a33ea099a9ca5d4e5da1/pircbot-1.5.0-sources.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/pircbot/pircbot/1.5.0/7a9dd235e6e81db733212202cc4067b5625650cf/pircbot-1.5.0.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.github.spotbugs/spotbugs-annotations/4.0.1/192f5794e6aa74968a883ecdf5bf7d961843579f/spotbugs-annotations-4.0.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.13.1/533f6ae0bb0ce091493f2eeab0c1df4327e46ef1/log4j-core-2.13.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.13.1/79e92fe5b6c30cc4c8a52893378d4d130e298c65/log4j-slf4j-impl-2.13.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.13.1/cc670f92dc77bbf4540904c3fa211b997cba00d8/log4j-api-2.13.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.9/122c7cee69b53ed4a7681c03d4ee4c0e2765da5/commons-lang3-3.9.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/commons-cli/commons-cli/1.4/c51c00206bb913cd8612b24abd9fa98ae89719b1/commons-cli-1.4.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/commons-net/commons-net/3.6/b71de00508dcb078d2b24b5fa7e538636de9b3da/commons-net-3.6.jar;K:/maven/repository/net/thauvin/erik/pinboard-poster/1.0.1/pinboard-poster-1.0.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/net.aksingh/owm-japis/2.5.3.0/c3aca5d34ba937e0c8e9776cec906003b0703044/owm-japis-2.5.3.0.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/converter-gson/2.5.0/1c96fc5d0230f57d36cd09e2541d10829a3352a7/converter-gson-2.5.0.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.squareup.retrofit2/retrofit/2.5.0/713ce36037bf24a76a3974c05cb85c3f754b1cc3/retrofit-2.5.0.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/4.4.1/d2e71a032b1927539c07adc3a9f36336d6fbf4b9/okhttp-4.4.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome/1.12.2/eaa7a2025cd38a6678d96b2b78b4f7e68e3f8e36/rome-1.12.2.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.json/json/20190722/7bce7bacf0ab5e9f894d307a3de8b7f540064d5/json-20190722.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.jsoup/jsoup/1.13.1/f9577f3732bb7caa4fee8aba5053158f4010c118/jsoup-1.13.1.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/net.objecthunter/exp4j/0.4.8/cf1cfc0f958077d86ac7452c7e36d944689b2ec4/exp4j-0.4.8.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.twitter4j/twitter4j-core/4.0.7/5fdb375ccfb3eda7354efb262cbe9b53abccff2/twitter4j-core-4.0.7.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.3.71/4defc79915cf4f78b49bbc4a8f1e80e87767a5b/kotlin-stdlib-jdk8-1.3.71.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.3.71/9180d3aec3f0b2ea6ef0dcf01b464a6e2219e381/kotlin-stdlib-jdk7-1.3.71.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.rometools/rome-utils/1.12.2/240dc40fb9333ac872319e7d31178bffc63f7900/rome-utils-1.12.2.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/2.4.3/d946f785445d73c6bc99bbd778576d2576e37ea9/okio-jvm-2.4.3.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.71/898273189ad22779da6bed88ded39b14cb5fd432/kotlin-stdlib-1.3.71.jar;K:/maven/repository/org/jdom/jdom2/2.0.6/jdom2-2.0.6.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/f645ed69d595b24d4cf8b3fbb64cc505bede8829/gson-2.8.5.jar;C:/Users/erik/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.71/e71c3fef58e26affeb03d675e91fd8abdd44aa7b/kotlin-stdlib-common-1.3.71.jar;K:/maven/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<option name="noStdlib" value="true" />
<option name="noReflect" value="true" />
<option name="moduleName" value="mobibot" />
<option name="languageVersion" value="1.3" />
<option name="apiVersion" value="1.3" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.3.71/7f5522e7a9d1736fabfdb4335630f64504ce8f20/kotlin-script-runtime-1.3.71.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.3.71/80b8eb0986d1391e6a1dcf9aba968d59165dc4f/kotlin-scripting-common-1.3.71.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.3.71/ee1ba4f199415255ea6ea55cc5dc41856b2729fd/kotlin-scripting-jvm-1.3.71.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.71/e71c3fef58e26affeb03d675e91fd8abdd44aa7b/kotlin-stdlib-common-1.3.71.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.71/898273189ad22779da6bed88ded39b14cb5fd432/kotlin-stdlib-1.3.71.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.2.1/3839faf625f4197acaeceeb6da000f011a2acb49/kotlinx-coroutines-core-1.2.1.jar" />
<option value="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
</array>
</option>
<option name="errors">
<ArgumentParseErrors />
</option>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_14">
<output url="file://$MODULE_DIR$/../../build/classes/java/main" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../src/generated/java">
<sourceFolder url="file://$MODULE_DIR$/../../src/generated/java" isTestSource="false" generated="true" />
</content>
<content url="file://$MODULE_DIR$/../../src/main">
<sourceFolder url="file://$MODULE_DIR$/../../src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../src/main/resources" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<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.0.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-core:2.13.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-slf4j-impl:2.13.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.logging.log4j:log4j-api:2.13.1" level="project" />
<orderEntry type="library" name="Gradle: org.apache.commons:commons-lang3:3.9" 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.6" level="project" />
<orderEntry type="library" name="Gradle: net.thauvin.erik:pinboard-poster:1.0.1" level="project" />
<orderEntry type="library" name="Gradle: net.aksingh:owm-japis:2.5.3.0" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.okhttp3:okhttp:4.4.1" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome:1.12.2" level="project" />
<orderEntry type="library" name="Gradle: org.json:json:20190722" level="project" />
<orderEntry type="library" name="Gradle: org.jsoup:jsoup:1.13.1" level="project" />
<orderEntry type="library" name="Gradle: net.objecthunter:exp4j:0.4.8" level="project" />
<orderEntry type="library" name="Gradle: org.twitter4j:twitter4j-core:4.0.7" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.71" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Gradle: com.google.code.findbugs:jsr305:3.0.2" 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:1.3.71" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.retrofit2:converter-gson:2.5.0" level="project" />
<orderEntry type="library" name="Gradle: com.squareup.retrofit2:retrofit: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.okio:okio:2.4.3" level="project" />
<orderEntry type="library" name="Gradle: com.rometools:rome-utils:1.12.2" level="project" />
<orderEntry type="library" name="Gradle: org.jdom:jdom2:2.0.6" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains:annotations:13.0" level="project" />
</component>
</module>

File diff suppressed because one or more lines are too long

10
.idea/runConfigurations.xml generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

2
.idea/vcs.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -1,26 +0,0 @@
language: java
dist: trusty
env:
global:
- CI=true
install:
- git fetch --unshallow --tags
addons:
sonarcloud:
organization: "ethauvin-github"
jdk:
- oraclejdk8
- openjdk12
before_install:
- chmod +x gradlew
after_success:
- |
if [ "${TRAVIS_TEST_RESULT}" == 0 ] && [ "$TRAVIS_JDK_VERSION" == oraclejdk8 ]; then
./gradlew sonarqube
fi

View file

@ -1,4 +1,4 @@
Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -1,4 +1,4 @@
[![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](http://opensource.org/licenses/BSD-3-Clause) [![Known Vulnerabilities](https://snyk.io/test/github/ethauvin/mobibot/badge.svg?targetFile=build.gradle)](https://snyk.io/test/github/ethauvin/mobibot?targetFile=build.gradle) [![Build Status](https://travis-ci.com/ethauvin/mobibot.svg?branch=master)](https://travis-ci.com/ethauvin/mobibot) [![CircleCI](https://circleci.com/gh/ethauvin/mobibot/tree/master.svg?style=shield)](https://circleci.com/gh/ethauvin/mobibot/tree/master)
[![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](http://opensource.org/licenses/BSD-3-Clause) [![Known Vulnerabilities](https://snyk.io/test/github/ethauvin/mobibot/badge.svg?targetFile=build.gradle)](https://snyk.io/test/github/ethauvin/mobibot?targetFile=build.gradle) [![GitHub CI](https://github.com/ethauvin/mobibot/actions/workflows/gradle.yml/badge.svg)](https://github.com/ethauvin/mobibot/actions/workflows/gradle.yml) [![CircleCI](https://circleci.com/gh/ethauvin/mobibot/tree/master.svg?style=shield)](https://circleci.com/gh/ethauvin/mobibot/tree/master)
Some very basic instructions:

9
bitbucket-pipelines.yml Normal file
View file

@ -0,0 +1,9 @@
image: openjdk:17
pipelines:
default:
- step:
caches:
- gradle
script:
- bash ./gradlew check

View file

@ -1,15 +1,14 @@
plugins {
id 'application'
id 'checkstyle'
id 'com.github.ben-manes.versions' version '0.28.0'
id 'com.github.spotbugs' version '4.0.4'
id 'com.github.ben-manes.versions' version '0.41.0'
id 'idea'
id 'io.gitlab.arturbosch.detekt' version '1.7.0'
id 'jacoco'
id 'io.gitlab.arturbosch.detekt' version '1.19.0'
id 'java'
id 'net.thauvin.erik.gradle.semver' version '1.0.4'
id 'org.jetbrains.kotlin.jvm' version '1.3.71'
id 'org.sonarqube' version '2.8'
id 'org.jetbrains.kotlin.jvm' version '1.6.10'
id 'org.jetbrains.kotlin.kapt' version '1.6.10'
id 'org.jetbrains.kotlinx.kover' version '0.4.4'
id 'org.sonarqube' version '3.3'
id 'pmd'
}
@ -19,83 +18,112 @@ final def packageName = 'net.thauvin.erik.mobibot'
final def deployDir = 'deploy'
final def semverProcessor = "net.thauvin.erik:semver:1.2.0"
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA', 'JRE'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r)?$/
return !stableKeyword && !(version ==~ regex)
}
mainClassName = packageName + '.Mobibot'
ext {
versions = [
kotlin : '1.3.71',
log4j : '2.13.1',
spotbugs : '4.0.1'
]
}
ext.versions = [
log4j: '2.17.1',
pmd : '6.41.0',
]
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
dependencies {
annotationProcessor semverProcessor
compileOnly semverProcessor
kapt(semverProcessor)
compileOnly(semverProcessor)
implementation 'pircbot:pircbot:1.5.0'
compileOnly 'pircbot:pircbot:1.5.0:sources'
implementation 'com.github.pircbotx:pircbotx:-SNAPSHOT'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-text:1.9'
implementation 'commons-cli:commons-cli:1.5.0'
implementation 'commons-codec:commons-codec:1.15'
implementation 'commons-net:commons-net:3.8.0'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.google.guava:guava:31.0.1-jre'
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.slf4j:slf4j-api:1.7.33'
implementation "org.apache.logging.log4j:log4j-api:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-core:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-slf4j-impl:$versions.log4j"
implementation 'org.apache.commons:commons-lang3:3.9'
implementation 'commons-cli:commons-cli:1.4'
implementation 'commons-net:commons-net:3.6'
implementation 'com.squareup.okhttp3:okhttp:4.4.1'
implementation 'com.rometools:rome:1.12.2'
implementation 'org.json:json:20190722'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'net.objecthunter:exp4j:0.4.8'
implementation 'org.twitter4j:twitter4j-core:4.0.7'
implementation 'net.thauvin.erik:pinboard-poster:1.0.1'
implementation 'com.rometools:rome:1.18.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'net.aksingh:owm-japis:2.5.3.0'
implementation 'net.objecthunter:exp4j:0.4.8'
implementation 'org.json:json:20211205'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'org.twitter4j:twitter4j-core:4.0.7'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
implementation 'net.thauvin.erik:cryptoprice:0.9.0'
implementation 'net.thauvin.erik:pinboard-poster:1.0.3'
testImplementation 'org.testng:testng:7.2.0'
testImplementation 'org.assertj:assertj-core:3.15.0'
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.1'
spotbugsPlugins 'com.mebigfatguy.sb-contrib:sb-contrib:7.4.7'
compileOnly "com.github.spotbugs:spotbugs-annotations:$versions.spotbugs"
testCompileOnly "com.github.spotbugs:spotbugs-annotations:$versions.spotbugs"
testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.25'
// testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
// testImplementation "org.mockito:mockito-core:4.0.0"
testImplementation 'org.testng:testng:7.5'
}
test {
testLogging {
exceptionFormat = 'full'
events('skipped', 'failed')
}
useTestNG() {
options.suites('src/test/resources/testng.xml')
}
}
spotbugs {
toolVersion.set("$versions.spotbugs")
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
reports {
xml.enabled = false
html.enabled = true
kapt {
includeCompileClasspath = false
arguments {
arg('semver.project.dir', projectDir)
}
excludeFilter.set(file("$projectDir/config/spotbugs/excludeFilter.xml"))
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
compileJava {
dependsOn 'incrementBuildMeta'
options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = java.targetCompatibility.toString()
}
}
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(it.candidate.version)
}
}
pmd {
toolVersion = '6.22.0'
toolVersion = versions.pmd
ignoreFailures = true
ruleSets = []
ruleSetFiles = files("${projectDir}/config/pmd.xml")
@ -103,34 +131,15 @@ pmd {
}
detekt {
baseline = file("detekt-baseline.xml")
}
tasks.withType(io.gitlab.arturbosch.detekt.Detekt) {
exclude("**/**.java")
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.annotationProcessorGeneratedSourcesDirectory = file("${projectDir}/src/generated/java")
options.compilerArgs += [ "-Asemver.project.dir=$projectDir" ]
}
compileJava {
dependsOn('incrementBuildMeta')
options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
}
javadoc {
options.tags = ['created']
options.author = true
options.links('http://www.jibble.org/javadocs/pircbot/', 'http://docs.oracle.com/javase/8/docs/api/')
//toolVersion = "main-SNAPSHOT"
baseline = file("${projectDir}/config/detekt/baseline.xml")
}
jar {
manifest.attributes('Main-Class': mainClassName,
'Class-Path': '. ./lib/' + configurations.runtimeClasspath.collect { it.getName() }.join(' ./lib/'))
archiveVersion.set("")
exclude('log4j2.xml')
}
clean {
@ -140,7 +149,7 @@ clean {
}
run {
args '--v'
args('-h')
}
incrementBuildMeta {
@ -148,42 +157,26 @@ incrementBuildMeta {
if (!System.getenv('CI')) {
buildMeta = sprintf("%03d", (buildMeta as Integer) + 1)
} else {
println "No increment on CIs."
println 'No increment on CIs.'
}
}
}
sonarqube {
properties {
property "sonar.projectKey", "ethauvin_mobibot"
}
}
jacoco {
toolVersion = '0.8.5'
}
jacocoTestReport {
reports {
html.enabled = true
xml.enabled = true
}
}
tasks.withType(Checkstyle) {
reports {
xml.enabled = false
html.enabled = true
property('sonar.organization', 'ethauvin-github')
property('sonar.projectKey', 'ethauvin_mobibot')
property('sonar.host.url', 'https://sonarcloud.io')
property('sonar.coverage.jacoco.xmlReportPaths', "${project.buildDir}/reports/kover/report.xml")
}
}
tasks.sonarqube {
dependsOn("jacocoTestReport")
dependsOn 'koverReport'
}
task copyToDeploy(type: Copy) {
from('properties')
from jar
from('properties', jar)
into deployDir
}
@ -191,22 +184,23 @@ task copyToDeployLib(type: Copy) {
from(configurations.runtimeClasspath) {
exclude 'annotations-*.jar'
}
into deployDir + '/lib'
into(deployDir + '/lib')
}
task deploy(dependsOn: ['clean', 'build', 'jar']) {
description = 'Copies all needed files to the ${deployDir} directory.'
task deploy {
description = "Copies all needed files to the ${deployDir} directory."
group = 'Publishing'
dependsOn(clean, build, jar)
outputs.dir deployDir
inputs.files copyToDeploy
inputs.files copyToDeployLib
inputs.files(copyToDeploy, copyToDeployLib)
doLast {
file(deployDir + '/logs').mkdir()
}
mustRunAfter clean
}
task release(dependsOn: ['wrapper', 'deploy']) {
task release {
group = 'Publishing'
description = 'Releases new version.'
dependsOn(wrapper, 'deploy')
}

View file

@ -1,277 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html.
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="LineLength">
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="SuppressWarningsFilter"/>
<module name="SuppressWithPlainTextCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE_OFF"/>
<property name="onCommentFormat" value="CHECKSTYLE_ON"/>
<property name="checkFormat"
value="^((?!(FileTabCharacterCheck)).)*$"/>
</module>
<module name="TreeWalker">
<module name="SuppressWarningsHolder"/>
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE_OFF: ALMOST_ALL"/>
<property name="onCommentFormat" value="CHECKSTYLE_ON: ALMOST_ALL"/>
<property name="checkFormat" value="^((?!(EqualsHashCode)).)*$"/>
</module>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="customImportOrderRules"
value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/>
<property name="specialImportsRegExp" value="^javax\."/>
<property name="standardPackageRegExp" value="^java\."/>
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="false"/>
</module>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments"
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<!-- <module name="CommentsIndentation"/> -->
</module>
</module>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues>
<ID>ComplexMethod:FeedsMgr.kt$FeedsMgr.Companion$ @JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
<ID>ComplexMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongMethod:FeedsMgr.kt$FeedsMgr.Companion$ @JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
<ID>LongMethod:Mobibot.kt$Mobibot.Companion$@JvmStatic @Throws(Exception::class) fun main(args: Array&lt;String&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$( channel: String, cmd: String, entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent )</ID>
<ID>LongParameterList:EntryLink.kt$EntryLink$( // Link's comments val comments: MutableList&lt;EntryComment&gt; = mutableListOf(), // Tags/categories val tags: MutableList&lt;SyndCategory&gt; = mutableListOf(), // Channel var channel: String, // Creation date var date: Date = Calendar.getInstance().time, // Link's URL var link: String, // Author's login var login: String = "", // Author's nickname var nick: String, // Link's title var title: String )</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$4</ID>
<ID>MagicNumber:CurrencyConverter.kt$CurrencyConverter.Companion$8</ID>
<ID>MagicNumber:Cycle.kt$Cycle$10</ID>
<ID>MagicNumber:Cycle.kt$Cycle$1000L</ID>
<ID>MagicNumber:Ignore.kt$Ignore$8</ID>
<ID>MagicNumber:Mobibot.kt$Mobibot$8</ID>
<ID>MagicNumber:Modules.kt$Modules$7</ID>
<ID>MagicNumber:StockQuote.kt$StockQuote.Companion$10</ID>
<ID>MagicNumber:Tell.kt$Tell$50</ID>
<ID>MagicNumber:Tell.kt$Tell$7</ID>
<ID>MagicNumber:Twitter.kt$Twitter$1000L</ID>
<ID>MagicNumber:Twitter.kt$Twitter$60L</ID>
<ID>MagicNumber:TwitterOAuth.kt$TwitterOAuth$401</ID>
<ID>MagicNumber:Users.kt$Users$8</ID>
<ID>MagicNumber:Utils.kt$Utils$30</ID>
<ID>MagicNumber:Utils.kt$Utils$365</ID>
<ID>MagicNumber:Utils.kt$Utils$7</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$1.60934</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$32</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$404</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$5</ID>
<ID>MagicNumber:Weather2.kt$Weather2.Companion$9</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime$14</ID>
<ID>MagicNumber:WorldTime.kt$WorldTime$4</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>NestedBlockDepth:Addons.kt$Addons$ fun add(command: AbstractCommand)</ID>
<ID>NestedBlockDepth:Addons.kt$Addons$ fun add(module: AbstractModule)</ID>
<ID>NestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
<ID>NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$ @JvmStatic fun convertCurrency(query: String): Message</ID>
<ID>NestedBlockDepth:EntryLink.kt$EntryLink$ private fun setTags(tags: List&lt;String?&gt;)</ID>
<ID>NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$ @JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String</ID>
<ID>NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$ @JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch.Companion$ @JvmStatic @Throws(ModuleException::class) fun searchGoogle(query: String, apiKey: String?, cseKey: String?): List&lt;Message&gt;</ID>
<ID>NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
<ID>NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
<ID>NestedBlockDepth:Posting.kt$Posting$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
<ID>NestedBlockDepth:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>NestedBlockDepth:Tell.kt$Tell$ fun send(event: GenericUserEvent)</ID>
<ID>NestedBlockDepth:TellMessagesMgr.kt$TellMessagesMgr$ @JvmStatic fun load(file: String): List&lt;TellMessage&gt;</ID>
<ID>NestedBlockDepth:TellMessagesMgr.kt$TellMessagesMgr$ @JvmStatic fun save(file: String, messages: List&lt;TellMessage?&gt;?)</ID>
<ID>NestedBlockDepth:TwitterOAuth.kt$TwitterOAuth$ @JvmStatic @Throws(TwitterException::class, IOException::class) fun main(args: Array&lt;String&gt;)</ID>
<ID>NestedBlockDepth:Weather2.kt$Weather2$ override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
<ID>NestedBlockDepth:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>ReturnCount:Addons.kt$Addons$ fun exec(channel: String, cmd: String, args: String, event: GenericMessageEvent): Boolean</ID>
<ID>ReturnCount:Addons.kt$Addons$ fun help(channel: String, topic: String, event: GenericMessageEvent): Boolean</ID>
<ID>ReturnCount:ExceptionSanitizer.kt$ExceptionSanitizer$ fun ModuleException.sanitize(vararg sanitize: String): ModuleException</ID>
<ID>SwallowedException:GoogleSearchTest.kt$GoogleSearchTest$e: ModuleException</ID>
<ID>SwallowedException:StockQuoteTest.kt$StockQuoteTest$e: ModuleException</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:StockQuote.kt$StockQuote.Companion$e: NullPointerException</ID>
<ID>TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException</ID>
<ID>TooManyFunctions:Tell.kt$Tell : AbstractCommand</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -5,111 +5,78 @@
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>Erik's Ruleset</description>
<!-- BEST PRACTICES -->
<!-- <rule ref="category/java/bestpractices.xml/AvoidStringBufferField"/>-->
<rule ref="category/java/bestpractices.xml/AvoidUsingHardCodedIP"/>
<rule ref="category/java/bestpractices.xml/CheckResultSet"/>
<rule ref="category/java/bestpractices.xml/ConstantsInInterface"/>
<rule ref="category/java/bestpractices.xml/DefaultLabelNotLastInSwitchStmt"/>
<rule ref="category/java/bestpractices.xml/ForLoopCanBeForeach"/>
<rule ref="category/java/bestpractices.xml/GuardLogStatement"/>
<!-- <rule ref="category/java/bestpractices.xml/LooseCoupling"/> -->
<rule ref="category/java/bestpractices.xml/MethodReturnsInternalArray"/>
<rule ref="category/java/bestpractices.xml">
<exclude name="AvoidPrintStackTrace"/>
<exclude name="JUnit4TestShouldUseTestAnnotation"/>
<exclude name="JUnitTestContainsTooManyAsserts"/>
</rule>
<rule ref="category/java/bestpractices.xml/MissingOverride">
<properties>
<property name="violationSuppressXPath"
value="./MethodDeclarator[@Image='hashCode' or @Image='equals' or @Image='toString']"/>
value="//MethodDeclaration[@Name='hashCode' or @Name='equals' or @Name='toString']"/>
</properties>
</rule>
<rule ref="category/java/bestpractices.xml/PositionLiteralsFirstInCaseInsensitiveComparisons"/>
<rule ref="category/java/bestpractices.xml/PositionLiteralsFirstInComparisons"/>
<rule ref="category/java/bestpractices.xml/PreserveStackTrace"/>
<rule ref="category/java/bestpractices.xml/ReplaceEnumerationWithIterator"/>
<!-- <rule ref="category/java/bestpractices.xml/ReplaceHashtableWithMap"/>
<rule ref="category/java/bestpractices.xml/ReplaceVectorWithList"/> -->
<rule ref="category/java/bestpractices.xml/SwitchStmtsShouldHaveDefault"/>
<rule ref="category/java/bestpractices.xml/SystemPrintln"/>
<rule ref="category/java/bestpractices.xml/UnusedFormalParameter"/>
<rule ref="category/java/bestpractices.xml/UnusedImports"/>
<rule ref="category/java/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateField"/>
<rule ref="category/java/bestpractices.xml/UnusedPrivateMethod"/>
<rule ref="category/java/bestpractices.xml/UseAssertEqualsInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertNullInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertSameInsteadOfAssertTrue"/>
<rule ref="category/java/bestpractices.xml/UseAssertTrueInsteadOfAssertEquals"/>
<rule ref="category/java/bestpractices.xml/UseCollectionIsEmpty"/>
<rule ref="category/java/bestpractices.xml/UseVarargs"/>
<!-- NAMING CONVENTIONS -->
<rule ref="category/java/codestyle.xml/FormalParameterNamingConventions"/>
<rule ref="category/java/codestyle.xml/GenericsNaming"/>
<rule ref="category/java/codestyle.xml/LocalVariableNamingConventions"/>
<rule ref="category/java/codestyle.xml/MethodNamingConventions"/>
<!-- <rule ref="category/java/codestyle.xml/PackageCase"/>-->
<!-- OTHER -->
<rule ref="category/java/codestyle.xml/AvoidDollarSigns"/>
<rule ref="category/java/codestyle.xml/AvoidProtectedFieldInFinalClass"/>
<rule ref="category/java/codestyle.xml/AvoidProtectedMethodInFinalClassNotExtending"/>
<rule ref="category/java/codestyle.xml/AvoidUsingNativeCode"/>
<rule ref="category/java/codestyle.xml/BooleanGetMethodName"/>
<rule ref="category/java/codestyle.xml/CallSuperInConstructor"/>
<rule ref="category/java/codestyle.xml/ControlStatementBraces"/>
<rule ref="category/java/codestyle.xml/DontImportJavaLang"/>
<rule ref="category/java/codestyle.xml/DuplicateImports"/>
<rule ref="category/java/codestyle.xml/EmptyMethodInAbstractClassShouldBeAbstract"/>
<rule ref="category/java/codestyle.xml/ExtendsObject"/>
<rule ref="category/java/codestyle.xml/FieldDeclarationsShouldBeAtStartOfClass"/>
<rule ref="category/java/codestyle.xml/ForLoopShouldBeWhileLoop"/>
<rule ref="category/java/codestyle.xml/IdenticalCatchBranches"/>
<rule ref="category/java/codestyle.xml/LocalVariableCouldBeFinal"/>
<rule ref="category/java/codestyle.xml/MethodArgumentCouldBeFinal"/>
<rule ref="category/java/codestyle.xml/NoPackage"/>
<rule ref="category/java/codestyle.xml/PrematureDeclaration"/>
<rule ref="category/java/codestyle.xml/TooManyStaticImports"/>
<rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement"/>
<rule ref="category/java/codestyle.xml/UnnecessaryConstructor"/>
<rule ref="category/java/codestyle.xml/UnnecessaryFullyQualifiedName"/>
<rule ref="category/java/codestyle.xml/UnnecessaryLocalBeforeReturn"/>
<rule ref="category/java/codestyle.xml/UnnecessaryReturn"/>
<rule ref="category/java/codestyle.xml/UselessQualifiedThis"/>
<!-- CODE STYLE -->
<rule ref="category/java/codestyle.xml">
<exclude name="AtLeastOneConstructor"/>
<exclude name="ClassNamingConventions"/>
<exclude name="ConfusingTernary"/>
<exclude name="CommentDefaultAccessModifier"/>
<exclude name="DefaultPackage"/>
<exclude name="FieldNamingConventions"/>
<exclude name="LongVariable"/>
<exclude name="OnlyOneReturn"/>
<exclude name="PackageCase"/>
<exclude name="ShortClassName"/>
<exclude name="ShortMethodName"/>
<exclude name="ShortVariable"/>
<exclude name="UselessParentheses"/>
<exclude name="UseUnderscoresInNumericLiterals"/>
</rule>
<!-- DESIGN -->
<rule ref="category/java/design.xml/AbstractClassWithoutAnyMethod"/>
<rule ref="category/java/design.xml/AvoidRethrowingException"/>
<rule ref="category/java/design.xml/AvoidThrowingNewInstanceOfSameException"/>
<rule ref="category/java/design.xml/AvoidThrowingNullPointerException"/>
<rule ref="category/java/design.xml/AvoidThrowingRawExceptionTypes"/>
<rule ref="category/java/design.xml/ClassWithOnlyPrivateConstructorsShouldBeFinal"/>
<rule ref="category/java/design.xml/CollapsibleIfStatements"/>
<rule ref="category/java/design.xml/CouplingBetweenObjects"/>
<rule ref="category/java/design.xml/DataClass"/>
<rule ref="category/java/design.xml/DoNotExtendJavaLangError"/>
<rule ref="category/java/design.xml/ExceptionAsFlowControl"/>
<!-- <rule ref="category/java/design.xml/ExcessivePublicCount"/>-->
<rule ref="category/java/design.xml/FinalFieldCouldBeStatic"/>
<rule ref="category/java/design.xml/ImmutableField"/>
<rule ref="category/java/design.xml/LogicInversion"/>
<rule ref="category/java/design.xml/SignatureDeclareThrowsException"/>
<rule ref="category/java/design.xml/SimplifiedTernary"/>
<rule ref="category/java/design.xml/SimplifyBooleanAssertion"/>
<rule ref="category/java/design.xml/SimplifyBooleanExpressions"/>
<rule ref="category/java/design.xml/SimplifyBooleanReturns"/>
<rule ref="category/java/design.xml/SimplifyConditional"/>
<rule ref="category/java/design.xml/SingularField"/>
<rule ref="category/java/design.xml/SwitchDensity"/>
<rule ref="category/java/design.xml/UselessOverridingMethod"/>
<rule ref="category/java/design.xml/UseUtilityClass"/>
<rule ref="category/java/design.xml">
<exclude name="AvoidCatchingGenericException"/>
<exclude name="AvoidDeeplyNestedIfStmts"/>
<exclude name="AvoidUncheckedExceptionsInSignatures"/>
<exclude name="CognitiveComplexity"/>
<exclude name="CyclomaticComplexity"/>
<exclude name="ExcessiveClassLength"/>
<exclude name="ExcessiveMethodLength"/>
<exclude name="ExcessiveParameterList"/>
<exclude name="ExcessivePublicCount"/>
<exclude name="GodClass"/>
<exclude name="LawOfDemeter"/>
<exclude name="LoosePackageCoupling"/>
<exclude name="NPathComplexity"/>
<exclude name="NcssCount"/>
<exclude name="TooManyFields"/>
<exclude name="TooManyMethods"/>
<exclude name="UseObjectForClearerAPI"/>
</rule>
<!-- DOCUMENTATION -->
<rule ref="category/java/documentation.xml/UncommentedEmptyConstructor"/>
<rule ref="category/java/documentation.xml/UncommentedEmptyMethodBody"/>
<rule ref="category/java/documentation.xml">
<exclude name="CommentRequired"/>
<exclude name="CommentSize"/>
</rule>
<!-- ERROR PRONE -->
<rule ref="category/java/errorprone.xml">
<exclude name="AssignmentInOperand"/>
<exclude name="AvoidCatchingNPE"/>
<exclude name="AvoidDuplicateLiterals"/>
<exclude name="AvoidFieldNameMatchingMethodName"/>
<exclude name="AvoidFieldNameMatchingTypeName"/>
<exclude name="AvoidLiteralsInIfCondition"/>
<exclude name="BeanMembersShouldSerialize"/>
<exclude name="EmptyCatchBlock"/>
<exclude name="NullAssignment"/>
</rule>
<rule ref="category/java/errorprone.xml/AssignmentInOperand">
<properties>
<property name="allowWhile" value="true"/>
@ -117,86 +84,11 @@
<property name="allowIf" value="true"/>
</properties>
</rule>
<rule ref="category/java/errorprone.xml/AssignmentToNonFinalStatic"/>
<rule ref="category/java/errorprone.xml/AvoidAccessibilityAlteration"/>
<rule ref="category/java/errorprone.xml/AvoidAssertAsIdentifier"/>
<rule ref="category/java/errorprone.xml/AvoidBranchingStatementAsLastInLoop"/>
<rule ref="category/java/errorprone.xml/AvoidCallingFinalize"/>
<rule ref="category/java/errorprone.xml/AvoidCatchingThrowable"/>
<rule ref="category/java/errorprone.xml/AvoidDecimalLiteralsInBigDecimalConstructor"/>
<rule ref="category/java/errorprone.xml/AvoidDuplicateLiterals">
<properties>
<property name="skipAnnotations" value="true"/>
</properties>
</rule>
<rule ref="category/java/errorprone.xml/AvoidEnumAsIdentifier"/>
<rule ref="category/java/errorprone.xml/AvoidInstanceofChecksInCatchClause"/>
<rule ref="category/java/errorprone.xml/AvoidLosingExceptionInformation"/>
<rule ref="category/java/errorprone.xml/AvoidMultipleUnaryOperators"/>
<rule ref="category/java/errorprone.xml/AvoidUsingOctalValues"/>
<rule ref="category/java/errorprone.xml/BadComparison"/>
<rule ref="category/java/errorprone.xml/BrokenNullCheck"/>
<rule ref="category/java/errorprone.xml/CallSuperFirst"/>
<rule ref="category/java/errorprone.xml/CallSuperLast"/>
<rule ref="category/java/errorprone.xml/CheckSkipResult"/>
<rule ref="category/java/errorprone.xml/ClassCastExceptionWithToArray"/>
<rule ref="category/java/errorprone.xml/CloneMethodMustBePublic"/>
<rule ref="category/java/errorprone.xml/CloneMethodMustImplementCloneable"/>
<rule ref="category/java/errorprone.xml/CloneMethodReturnTypeMustMatchClassName"/>
<rule ref="category/java/errorprone.xml/CloneThrowsCloneNotSupportedException"/>
<rule ref="category/java/errorprone.xml/CloseResource"/>
<rule ref="category/java/errorprone.xml/CompareObjectsWithEquals"/>
<rule ref="category/java/errorprone.xml/ConstructorCallsOverridableMethod"/>>
<rule ref="category/java/errorprone.xml/DoNotCallGarbageCollectionExplicitly"/>
<rule ref="category/java/errorprone.xml/DoNotExtendJavaLangThrowable"/>
<rule ref="category/java/errorprone.xml/DoNotHardCodeSDCard"/>
<rule ref="category/java/errorprone.xml/DoNotThrowExceptionInFinally"/>
<rule ref="category/java/errorprone.xml/DontImportSun"/>
<rule ref="category/java/errorprone.xml/DontUseFloatTypeForLoopIndices"/>
<rule ref="category/java/errorprone.xml/EqualsNull"/>
<rule ref="category/java/errorprone.xml/FinalizeDoesNotCallSuperFinalize"/>
<rule ref="category/java/errorprone.xml/FinalizeOnlyCallsSuperFinalize"/>
<rule ref="category/java/errorprone.xml/FinalizeOverloaded"/>
<rule ref="category/java/errorprone.xml/FinalizeShouldBeProtected"/>
<rule ref="category/java/errorprone.xml/IdempotentOperations"/>
<rule ref="category/java/errorprone.xml/ImportFromSamePackage"/>
<rule ref="category/java/errorprone.xml/InstantiationToGetClass"/>
<rule ref="category/java/errorprone.xml/InvalidLogMessageFormat"/>
<rule ref="category/java/errorprone.xml/JumbledIncrementer"/>
<rule ref="category/java/errorprone.xml/JUnitSpelling"/>
<rule ref="category/java/errorprone.xml/JUnitStaticSuite"/>
<rule ref="category/java/errorprone.xml/MethodWithSameNameAsEnclosingClass"/>
<rule ref="category/java/errorprone.xml/MisplacedNullCheck"/>
<rule ref="category/java/errorprone.xml/MissingBreakInSwitch"/>
<rule ref="category/java/errorprone.xml/MissingSerialVersionUID"/>
<rule ref="category/java/errorprone.xml/MissingStaticMethodInNonInstantiatableClass"/>
<rule ref="category/java/errorprone.xml/MoreThanOneLogger"/>
<rule ref="category/java/errorprone.xml/NonCaseLabelInSwitchStatement"/>
<rule ref="category/java/errorprone.xml/NonStaticInitializer"/>
<!-- <rule ref="category/java/errorprone.xml/NullAssignment"/>-->
<rule ref="category/java/errorprone.xml/OverrideBothEqualsAndHashcode"/>
<rule ref="category/java/errorprone.xml/ProperCloneImplementation"/>
<rule ref="category/java/errorprone.xml/ProperLogger"/>
<rule ref="category/java/errorprone.xml/ReturnEmptyArrayRatherThanNull"/>
<rule ref="category/java/errorprone.xml/ReturnFromFinallyBlock"/>
<rule ref="category/java/errorprone.xml/SimpleDateFormatNeedsLocale"/>
<rule ref="category/java/errorprone.xml/SingleMethodSingleton"/>
<rule ref="category/java/errorprone.xml/SingletonClassReturningNewInstance"/>
<rule ref="category/java/errorprone.xml/StaticEJBFieldShouldBeFinal"/>
<rule ref="category/java/errorprone.xml/StringBufferInstantiationWithChar"/>
<rule ref="category/java/errorprone.xml/SuspiciousEqualsMethodName"/>
<rule ref="category/java/errorprone.xml/SuspiciousHashcodeMethodName"/>
<rule ref="category/java/errorprone.xml/SuspiciousOctalEscape"/>
<rule ref="category/java/errorprone.xml/TestClassWithoutTestCases"/>
<rule ref="category/java/errorprone.xml/UnconditionalIfStatement"/>
<rule ref="category/java/errorprone.xml/UnnecessaryBooleanAssertion"/>
<rule ref="category/java/errorprone.xml/UnnecessaryCaseChange"/>
<rule ref="category/java/errorprone.xml/UnnecessaryConversionTemporary"/>
<rule ref="category/java/errorprone.xml/UnusedNullCheckInEquals"/>
<rule ref="category/java/errorprone.xml/UseCorrectExceptionLogging"/>
<rule ref="category/java/errorprone.xml/UseEqualsToCompareStrings"/>
<rule ref="category/java/errorprone.xml/UselessOperationOnImmutable"/>
<rule ref="category/java/errorprone.xml/UseLocaleWithCaseConversions"/>
<rule ref="category/java/errorprone.xml/EmptyCatchBlock">
<properties>
<property name="allowExceptionNameRegex">
@ -204,61 +96,16 @@
</property>
</properties>
</rule>
<rule ref="category/java/errorprone.xml/EmptyFinalizer"/>
<rule ref="category/java/errorprone.xml/EmptyFinallyBlock"/>
<rule ref="category/java/errorprone.xml/EmptyIfStmt"/>
<rule ref="category/java/errorprone.xml/EmptyInitializer"/>
<rule ref="category/java/errorprone.xml/EmptyStatementBlock"/>
<rule ref="category/java/errorprone.xml/EmptyStatementNotInLoop"/>
<rule ref="category/java/errorprone.xml/EmptySwitchStatements"/>
<rule ref="category/java/errorprone.xml/EmptySynchronizedBlock"/>
<rule ref="category/java/errorprone.xml/EmptyTryBlock"/>
<rule ref="category/java/errorprone.xml/EmptyWhileStmt"/>
<!-- MULTITHREADING -->
<rule ref="category/java/multithreading.xml/AvoidSynchronizedAtMethodLevel"/>
<rule ref="category/java/multithreading.xml/AvoidThreadGroup"/>
<rule ref="category/java/multithreading.xml/AvoidUsingVolatile"/>
<rule ref="category/java/multithreading.xml/DontCallThreadRun"/>
<rule ref="category/java/multithreading.xml/DoubleCheckedLocking"/>
<rule ref="category/java/multithreading.xml/NonThreadSafeSingleton"/>
<rule ref="category/java/multithreading.xml/UseConcurrentHashMap"/>
<rule ref="category/java/multithreading.xml/UseNotifyAllInsteadOfNotify"/>
<rule ref="category/java/multithreading.xml">
</rule>
<!-- PERFORMANCE -->
<rule ref="category/java/performance.xml/AddEmptyString"/>
<rule ref="category/java/performance.xml/AppendCharacterWithChar"/>
<rule ref="category/java/performance.xml/AvoidArrayLoops"/>
<rule ref="category/java/performance.xml/AvoidFileStream"/>
<rule ref="category/java/performance.xml/AvoidInstantiatingObjectsInLoops"/>
<rule ref="category/java/performance.xml/AvoidUsingShortType"/>
<rule ref="category/java/performance.xml/BigIntegerInstantiation"/>
<rule ref="category/java/performance.xml/BooleanInstantiation"/>
<rule ref="category/java/performance.xml/ByteInstantiation"/>
<rule ref="category/java/performance.xml/ConsecutiveAppendsShouldReuse"/>
<rule ref="category/java/performance.xml/ConsecutiveLiteralAppends"/>
<rule ref="category/java/performance.xml/InefficientEmptyStringCheck"/>
<rule ref="category/java/performance.xml/InefficientStringBuffering"/>
<rule ref="category/java/performance.xml/InsufficientStringBufferDeclaration"/>
<rule ref="category/java/performance.xml/IntegerInstantiation"/>
<rule ref="category/java/performance.xml/LongInstantiation"/>
<rule ref="category/java/performance.xml/OptimizableToArrayCall"/>
<rule ref="category/java/performance.xml/RedundantFieldInitializer"/>
<rule ref="category/java/performance.xml/SimplifyStartsWith"/>
<rule ref="category/java/performance.xml/ShortInstantiation"/>
<rule ref="category/java/performance.xml/StringInstantiation"/>
<rule ref="category/java/performance.xml/StringToString"/>
<rule ref="category/java/performance.xml/TooFewBranchesForASwitchStatement"/>
<rule ref="category/java/performance.xml/UnnecessaryWrapperObjectCreation"/>
<!-- <rule ref="category/java/performance.xml/UseArrayListInsteadOfVector"/> -->
<rule ref="category/java/performance.xml/UseArraysAsList"/>
<rule ref="category/java/performance.xml/UseIndexOfChar"/>
<rule ref="category/java/performance.xml/UselessStringValueOf"/>
<rule ref="category/java/performance.xml/UseStringBufferForStringAppends"/>
<rule ref="category/java/performance.xml/UseStringBufferLength"/>
<rule ref="category/java/performance.xml">
</rule>
<!-- SECURITY -->
<rule ref="category/java/security.xml/HardCodedCryptoKey"/>
<rule ref="category/java/security.xml/InsecureCryptoIv"/>
<rule ref="category/java/security.xml">
</rule>
</ruleset>

View file

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
<Match>
<Or>
<Package name="net.thauvin.erik.mobibot.*"/>
<Package name="net.thauvin.erik.mobibot.tell.*"/>
<Package name="net.thauvin.erik.mobibot.entries.*"/>
</Or>
<Or>
<Bug pattern="PATH_TRAVERSAL_IN"/>
<Bug pattern="PATH_TRAVERSAL_OUT"/>
</Or>
<Confidence value="2"/>
</Match>
<Match>
<Or>
<Class name="net.thauvin.erik.mobibot.Mobibot"/>
<Class name="net.thauvin.erik.mobibot.Pinboard"/>
<Class name="net.thauvin.erik.mobibot.FeedReader"/>
<Class name="net.thauvin.erik.mobibot.tell.Tell"/>
<Package name="net.thauvin.erik.mobibot.modules.*"/>
<Package name="net.thauvin.erik.mobibot.entries.*"/>
</Or>
<Bug pattern="FCCD_FIND_CLASS_CIRCULAR_DEPENDENCY"/>
</Match>
<Match>
<Class name="net.thauvin.erik.mobibot.Mobibot"/>
<Method name="main"/>
<Bug pattern="PATH_TRAVERSAL_OUT"/>
<Confidence value="1"/>
</Match>
</FindBugsFilter>

12
deploy.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
DEPLOYDIR=/home/erik/mobitopia/mobibot
if [ -f "deploy/mobibot.jar" ]; then
/bin/cp deploy/mobibot.jar $DEPLOYDIR
rm -rf $DEPLOYDIR/lib/*.jar
cp deploy/lib/*.jar $DEPLOYDIR/lib
chmod 755 $DEPLOYDIR/*.jar $DEPLOYDIR/lib/*.jar
else
echo "mobibot.jar not found."
fi

View file

@ -1,37 +0,0 @@
<?xml version="1.0" ?>
<!--
~ detekt-baseline.xml
~
~ 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.
-->
<SmellBaseline>
<Blacklist></Blacklist>
<Whitelist></Whitelist>
</SmellBaseline>

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-rc-3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored
View file

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,78 +17,113 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -105,79 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

22
gradlew.bat vendored
View file

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -64,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View file

@ -1,4 +1,4 @@
Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -1,16 +0,0 @@
License (http://ostermiller.org/utils/)
Copyright (C) 2004-2010 Stephen Ostermiller
http://ostermiller.org/contact.pl?regarding=Java+Utilities
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
See the GNU General Public License (https://ostermiller.org/utils/license.html) for more details.

View file

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2009-2018 Jonathan Hedley <jonathan@hedley.net>
Copyright (c) 2009-2022 Jonathan Hedley <jonathan@hedley.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,31 +0,0 @@
/*
* This file is automatically generated.
* Do not modify! -- ALL CHANGES WILL BE ERASED!
*/
package {{packageName}};
import java.time.*;
/**
* Provides semantic version information.
*
* @author <a href="https://github.com/ethauvin/semver">Semantic Version Annotation Processor</a>
*/
public final class {{className}} {
public static final String PROJECT = "{{project}}";
public static final LocalDateTime BUILDDATE =
LocalDateTime.ofInstant(Instant.ofEpochMilli({{epoch}}L), ZoneId.systemDefault());
public static final int MAJOR = {{major}};
public static final int MINOR = {{minor}};
public static final int PATCH = {{patch}};
public static final String PRERELEASE = "{{preRelease}}";
public static final String BUILDMETA = "{{buildMeta}}";
public static final String VERSION = "{{version}}";
/**
* Disables the default constructor.
*/
private {{className}}() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
}

View file

@ -1,16 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="stderr" target="SYSTEM_ERR">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="net.thauvin.erik.mobibot" level="warn" additivity="false">
<AppenderRef ref="stderr"/>
</Logger>
<Root level="error">
<AppenderRef ref="stderr"/>
</Root>
</Loggers>
</Configuration>
<Configuration status="warn">
<Appenders>
<Console name="stderr" target="SYSTEM_ERR">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<Console name="input" target="SYSTEM_OUT">
<PatternLayout pattern="%d{UNIX_MILLIS} %msg%n"/>
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Console>
<Console name="output" target="SYSTEM_OUT">
<PatternLayout pattern="%d{UNIX_MILLIS} >>>%msg%n"/>
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Console>
</Appenders>
<Loggers>
<Root level="warn" additivity="false">
<AppenderRef ref="stderr"/>
</Root>
<logger level="debug" name="org.pircbotx.InputParser" additivity="false">
<appender-ref ref="input"/>
<appender-ref ref="stderr" level="warn"/>
</logger>
<logger level="debug" name="org.pircbotx.output.OutputRaw" additivity="false">
<appender-ref ref="output"/>
<appender-ref ref="stderr" level="warn"/>
</logger>
<logger level="warn" name="net.thauvin.erik.mobibot" additivity="false">
<appender-ref ref="stderr"/>
</logger>
</Loggers>
</Configuration>

View file

@ -1,11 +1,15 @@
channel=#mobitopia
server=irc.freenode.net
server=irc.libera.chat
#port=6667
login=mobibot
nick=mobibot
#realname=mobibot
# Die command password, if any
#die=changeme
# NickServ password
ident=changepwd
ident=changeme
#ident-nick=nickserv
#ident-msg=IDENTIFY changepwd
@ -14,20 +18,22 @@ ignore=chanserv,nickserv
tags=mobile mobitopia
tags-keywords=android ios apple google
weblog=http://www.mobitopia.org/
feed=http://www.mobitopia.org/rss.xml
backlogs=http://www.mobitopia.org/mobibot/logs
tell-max-days=5
tell-max-size=50
#disabled-commands=die, ignore
#disabled-modules=dice, joke
#
# Credentials for: http://pinboard.in/
#
#pinboard-api-token=user\:TOKEN
#
# Configure app at: https://apps.twitter.com/
# Configure app at: https://developer.twitter.com/en/apps
# and then: java -cp mobibot.jar net.thauvin.erik.mobibot.TwitterOAuth <consumerKey> <consumerSecret>
#
#twitter-consumerKey=

View file

@ -1 +1,18 @@
plugins {
id "com.gradle.enterprise" version "3.6.3"
}
gradleEnterprise {
buildScan {
link("GitHub", "https://github.com/ethauvin/pinboard-poster/tree/master")
if (System.getenv("CI")) {
uploadInBackground = false
publishOnFailure()
tag "CI"
}
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
}
}
rootProject.name = 'mobibot'

View file

@ -1,31 +0,0 @@
/*
* This file is automatically generated.
* Do not modify! -- ALL CHANGES WILL BE ERASED!
*/
package net.thauvin.erik.mobibot;
import java.time.*;
/**
* Provides semantic version information.
*
* @author <a href="https://github.com/ethauvin/semver">Semantic Version Annotation Processor</a>
*/
public final class ReleaseInfo {
public static final String PROJECT = "mobibot";
public static final LocalDateTime BUILDDATE =
LocalDateTime.ofInstant(Instant.ofEpochMilli(1585119580379L), ZoneId.systemDefault());
public static final int MAJOR = 0;
public static final int MINOR = 7;
public static final int PATCH = 3;
public static final String PRERELEASE = "beta";
public static final String BUILDMETA = "760";
public static final String VERSION = "0.7.3-beta+760";
/**
* Disables the default constructor.
*/
private ReleaseInfo() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
}

View file

@ -1,149 +0,0 @@
/*
* Commands.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;
/**
* The <code>commands</code>, <code>keywords</code> and <code>arguments</code>.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-26
* @since 1.0
*/
public final class Commands {
/**
* The link command.
*/
public static final String LINK_CMD = "L";
/**
* The view command.
*/
public static final String VIEW_CMD = "view";
/**
* The add (back)log command.
*/
static final String ADDLOG_CMD = "addlog";
/**
* The cycle command.
*/
static final String CYCLE_CMD = "cycle";
/**
* Debug command line argument.
*/
static final String DEBUG_ARG = "debug";
/**
* The debug command.
*/
static final String DEBUG_CMD = "debug";
/**
* The die command.
*/
static final String DIE_CMD = "die";
/**
* Help command line argument.
*/
static final String HELP_ARG = "help";
/**
* The help command.
*/
static final String HELP_CMD = "help";
/**
* The help on posting keyword.
*/
static final String HELP_POSTING_KEYWORD = "posting";
/**
* The help on tags keyword.
*/
static final String HELP_TAGS_KEYWORD = "tags";
/**
* The ignore command.
*/
static final String IGNORE_CMD = "ignore";
/**
* The ignore <code>me</code> keyword.
*/
static final String IGNORE_ME_KEYWORD = "me";
/**
* The info command.
*/
static final String INFO_CMD = "info";
/**
* The me command.
*/
static final String ME_CMD = "me";
/**
* The modules command.
*/
static final String MODULES_CMD = "modules";
/**
* The msg command.
*/
static final String MSG_CMD = "msg";
/**
* The nick command.
*/
static final String NICK_CMD = "nick";
/**
* Properties command line argument.
*/
static final String PROPS_ARG = "properties";
/**
* The recap command.
*/
static final String RECAP_CMD = "recap";
/**
* The say command.
*/
static final String SAY_CMD = "say";
/**
* The users command.
*/
static final String USERS_CMD = "users";
/**
* Properties version line argument.
*/
static final String VERSION_ARG = "version";
/**
* The version command.
*/
static final String VERSION_CMD = "version";
/**
* Disables the default constructor.
*
* @throws UnsupportedOperationException If the constructor is called.
*/
private Commands() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
}

View file

@ -1,108 +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
*/
class FeedReader implements Runnable {
// Maximum number of feed items to display
private static final int MAX_ITEMS = 5;
// Tab indent (4 spaces)
private static final String TAB_INDENT = " ";
// 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.
*/
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();
final SyndFeed feed = input.build(new XmlReader(new URL(url)));
SyndEntry item;
final List<SyndEntry> items = feed.getEntries();
if (items.isEmpty()) {
bot.send(sender, "There is currently nothing to view. Why don't you post something?");
} else {
for (int i = 0; (i < items.size()) && (i < MAX_ITEMS); i++) {
item = items.get(i);
bot.send(sender, item.getTitle());
bot.send(sender, TAB_INDENT + Utils.green(item.getLink()));
}
}
} catch (MalformedURLException e) {
bot.getLogger().debug("Invalid feed URL.", e);
bot.send(sender, "The feed URL is invalid.");
} 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());
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,171 +0,0 @@
/*
* Pinboard.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;
import net.thauvin.erik.mobibot.entries.EntryLink;
import net.thauvin.erik.pinboard.PinboardPoster;
import javax.swing.SwingWorker;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The class to handle posts to pinboard.in.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2017-05-17
* @since 1.0
*/
class Pinboard {
private final String ircServer;
private final PinboardPoster pinboard;
/**
* Creates a new {@link Pinboard} instance.
*
* @param bot The bot's instance.
* @param apiToken The API end point.
* @param ircServer The IRC server.
*/
Pinboard(final Mobibot bot, final String apiToken, final String ircServer) {
pinboard = new PinboardPoster(apiToken);
this.ircServer = ircServer;
if (bot.getLogger().isDebugEnabled()) {
final ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.FINE);
final Logger logger = pinboard.getLogger();
logger.addHandler(consoleHandler);
logger.setLevel(Level.FINE);
}
}
/**
* Adds a post to pinboard.in.
*
* @param entry The entry to add.
*/
@SuppressWarnings("Convert2Diamond")
final void addPost(final EntryLink entry) {
final SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() {
return pinboard.addPin(entry.getLink(),
entry.getTitle(),
postedBy(entry),
entry.getPinboardTags(),
formatDate(entry.getDate()));
}
};
worker.execute();
}
/**
* Deletes a post to pinboard.in.
*
* @param entry The entry to delete.
*/
@SuppressWarnings("Convert2Diamond")
final void deletePost(final EntryLink entry) {
final String link = entry.getLink();
final SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() {
return pinboard.deletePin(link);
}
};
worker.execute();
}
/**
* Format a date to a UTC timestamp.
*
* @param date The date.
* @return The date in {@link DateTimeFormatter#ISO_INSTANT} format.
*/
private String formatDate(final Date date) {
return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).format(DateTimeFormatter.ISO_INSTANT);
}
/**
* Returns he pinboard.in extended attribution line.
*
* @param entry The entry.
* @return The extended attribution line.
*/
private String postedBy(final EntryLink entry) {
return "Posted by " + entry.getNick() + " on " + entry.getChannel() + " (" + ircServer + ')';
}
/**
* Updates a post to pinboard.in.
*
* @param oldUrl The old post URL.
* @param entry The entry to add.
*/
@SuppressWarnings("Convert2Diamond")
final void updatePost(final String oldUrl, final EntryLink entry) {
final SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() {
if (!oldUrl.equals(entry.getLink())) {
pinboard.deletePin(oldUrl);
return pinboard.addPin(entry.getLink(),
entry.getTitle(),
postedBy(entry),
entry.getPinboardTags(),
formatDate(entry.getDate()));
} else {
return pinboard.addPin(entry.getLink(),
entry.getTitle(),
postedBy(entry),
entry.getPinboardTags(),
formatDate(entry.getDate()),
true,
true);
}
}
};
worker.execute();
}
}

View file

@ -1,112 +0,0 @@
/*
* TwitterOAuth.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 twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* The <code>TwitterOAuth</code> class.
* <p>
* Go to <a href="http://twitter.com/oauth_clients/new">http://twitter.com/oauth_clients/new</a> to register your bot.
* </p>
* Then execute:
* <p>
* <code>
* java -cp "mobibot.jar:lib/*" net.thauvin.erik.mobibot.TwitterOAuth &lt;consumerKey&gt; &lt;consumerSecret&gt;
* </code>
* </p>
* and follow the prompts/instructions.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @author <a href="http://twitter4j.org/en/code-examples.html#oauth" target="_blank">Twitter4J</a>
* @created Sep 13, 2010
* @since 1.0
*/
@SuppressWarnings("PMD.UseUtilityClass")
public final class TwitterOAuth {
/**
* Twitter OAuth Client Registration.
*
* @param args The consumerKey and consumerSecret should be passed as arguments.
* @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"})
public static void main(final String[] args) throws TwitterException, IOException {
if (args.length == 2) {
final twitter4j.Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(args[0], args[1]);
final RequestToken requestToken = twitter.getOAuthRequestToken();
AccessToken accessToken = null;
try (final BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
while (null == accessToken) {
System.out.println("Open the following URL and grant access to your account:");
System.out.println(requestToken.getAuthorizationURL());
System.out.print("Enter the PIN (if available) or just hit enter.[PIN]:");
final String pin = br.readLine();
try {
if (pin != null && pin.length() > 0) {
accessToken = twitter.getOAuthAccessToken(requestToken, pin);
} else {
accessToken = twitter.getOAuthAccessToken();
}
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());
} catch (TwitterException te) {
if (401 == te.getStatusCode()) {
System.out.println("Unable to get the access token.");
} else {
te.printStackTrace();
}
}
}
}
} else {
System.out.println("Usage: " + TwitterOAuth.class.getName() + " <consumerKey> <consumerSecret>");
}
System.exit(0);
}
}

View file

@ -1,317 +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 org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors;
import org.jsoup.Jsoup;
import java.io.File;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 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 {
/**
* 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);
}
/**
* 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);
}
/**
* 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();
}
/**
* 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

@ -1,338 +0,0 @@
/*
* EntriesMgr.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.entries;
import com.rometools.rome.feed.synd.SyndContent;
import com.rometools.rome.feed.synd.SyndContentImpl;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndEntryImpl;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndFeedImpl;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.SyndFeedOutput;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
/**
* Manages the feed entries.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-28
* @since 1.0
*/
public final class EntriesMgr {
/**
* The name of the file containing the current entries.
*/
public static final String CURRENT_XML = "current.xml";
/**
* The name of the file containing the backlog entries.
*/
public static final String NAV_XML = "nav.xml";
/**
* The .xml extension
*/
public static final String XML_EXT = ".xml";
// Maximum number of backlogs to keep
private static final int MAX_BACKLOGS = 10;
/**
* Disables the default constructor.
*
* @throws UnsupportedOperationException If the constructor is called.
*/
private EntriesMgr() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Loads the backlogs.
*
* @param file The file containing the backlogs.
* @param history The history list.
* @throws IOException If the file was not found or could not be read.
* @throws FeedException If an error occurred while reading the feed.
*/
public static void loadBacklogs(final String file, final Collection<String> history)
throws IOException, FeedException {
history.clear();
final SyndFeedInput input = new SyndFeedInput();
try (final InputStreamReader reader =
new InputStreamReader(Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8)) {
final SyndFeed feed = input.build(reader);
final List<SyndEntry> items = feed.getEntries();
SyndEntry item;
for (int i = items.size() - 1; i >= 0; i--) {
item = items.get(i);
history.add(item.getTitle());
}
}
}
/**
* Loads the current entries.
*
* @param file The file containing the current entries.
* @param channel The channel
* @param entries The entries.
* @return The feed's last published date.
* @throws IOException If the file was not found or could not be read.
* @throws FeedException If an error occurred while reading the feed.
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public static String loadEntries(final String file, final String channel, final Collection<EntryLink> entries)
throws IOException, FeedException {
entries.clear();
final SyndFeedInput input = new SyndFeedInput();
final String today;
try (final InputStreamReader reader = new InputStreamReader(
Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8)) {
final SyndFeed feed = input.build(reader);
today = Utils.isoLocalDate(feed.getPublishedDate());
final List<SyndEntry> items = feed.getEntries();
SyndEntry item;
SyndContent description;
String[] comments;
String author;
EntryLink entry;
for (int i = items.size() - 1; i >= 0; i--) {
item = items.get(i);
author = item.getAuthor()
.substring(item.getAuthor().lastIndexOf('(') + 1, item.getAuthor().length() - 1);
entry = new EntryLink(item.getLink(),
item.getTitle(),
author,
channel,
item.getPublishedDate(),
item.getCategories());
description = item.getDescription();
comments = description.getValue().split("<br/>");
int split;
for (final String comment : comments) {
split = comment.indexOf(": ");
if (split != -1) {
entry.addComment(comment.substring(split + 2).trim(), comment.substring(0, split).trim());
}
}
entries.add(entry);
}
}
return today;
}
/**
* Saves the entries.
*
* @param bot The bot object.
* @param entries The entries array.
* @param history The history array.
* @param isDayBackup Set the true if the daily backup file should also be created.
*/
@SuppressFBWarnings(value = {"CE_CLASS_ENVY", "CC_CYCLOMATIC_COMPLEXITY"}, justification = "Yes, it does.")
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public static void saveEntries(final Mobibot bot,
final List<EntryLink> entries,
final List<String> history,
final boolean isDayBackup) {
if (bot.getLogger().isDebugEnabled()) {
bot.getLogger().debug("Saving the feeds...");
}
if (StringUtils.isNotBlank(bot.getLogsDir()) && StringUtils.isNotBlank(bot.getWeblogUrl())) {
try {
final SyndFeedOutput output = new SyndFeedOutput();
SyndFeed rss = new SyndFeedImpl();
final List<SyndEntry> items = new ArrayList<>(0);
SyndEntry item;
SyndContent description;
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.getLogsDir() + CURRENT_XML)), StandardCharsets.UTF_8)) {
rss.setFeedType("rss_2.0");
rss.setTitle(bot.getChannel() + " IRC Links");
rss.setDescription("Links from " + bot.getIrcServer() + " on " + bot.getChannel());
rss.setLink(bot.getWeblogUrl());
rss.setPublishedDate(Calendar.getInstance().getTime());
rss.setLanguage("en");
EntryLink entry;
StringBuilder buff;
EntryComment comment;
for (int i = (entries.size() - 1); i >= 0; --i) {
entry = entries.get(i);
buff = new StringBuilder()
.append("Posted by <b>")
.append(entry.getNick())
.append("</b> on <a href=\"irc://")
.append(bot.getIrcServer()).append('/')
.append(entry.getChannel())
.append("\"><b>")
.append(entry.getChannel())
.append("</b></a>");
if (entry.getCommentsCount() > 0) {
buff.append(" <br/><br/>");
final EntryComment[] comments = entry.getComments();
for (int j = 0; j < comments.length; j++) {
comment = comments[j];
if (j > 0) {
buff.append(" <br/>");
}
buff.append(comment.getNick()).append(": ").append(comment.getComment());
}
}
item = new SyndEntryImpl();
item.setLink(entry.getLink());
description = new SyndContentImpl();
description.setValue(buff.toString());
item.setDescription(description);
item.setTitle(entry.getTitle());
item.setPublishedDate(entry.getDate());
item.setAuthor(
bot.getChannel().substring(1) + '@' + bot.getIrcServer() + " (" + entry.getNick() + ')');
item.setCategories(entry.getTags());
items.add(item);
}
rss.setEntries(items);
if (bot.getLogger().isDebugEnabled()) {
bot.getLogger().debug("Writing the entries feed.");
}
output.output(rss, fw);
}
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(
bot.getLogsDir() + bot.getToday() + XML_EXT)), StandardCharsets.UTF_8)) {
output.output(rss, fw);
}
if (isDayBackup) {
if (StringUtils.isNotBlank(bot.getBacklogsUrl())) {
if (!history.contains(bot.getToday())) {
history.add(bot.getToday());
while (history.size() > MAX_BACKLOGS) {
history.remove(0);
}
}
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.getLogsDir() + NAV_XML)), StandardCharsets.UTF_8)) {
rss = new SyndFeedImpl();
rss.setFeedType("rss_2.0");
rss.setTitle(bot.getChannel() + " IRC Links Backlogs");
rss.setDescription("Backlogs of Links from " + bot.getIrcServer() + " on "
+ bot.getChannel());
rss.setLink(bot.getBacklogsUrl());
rss.setPublishedDate(Calendar.getInstance().getTime());
String date;
items.clear();
for (int i = (history.size() - 1); i >= 0; --i) {
date = history.get(i);
item = new SyndEntryImpl();
item.setLink(bot.getBacklogsUrl() + date + ".xml");
item.setTitle(date);
description = new SyndContentImpl();
description.setValue("Links for " + date);
item.setDescription(description);
items.add(item);
}
rss.setEntries(items);
if (bot.getLogger().isDebugEnabled()) {
bot.getLogger().debug("Writing the backlog feed.");
}
output.output(rss, fw);
}
} else {
bot.getLogger().warn("Unable to generate the backlogs feed. No property configured.");
}
}
} catch (FeedException | IOException e) {
bot.getLogger().warn("Unable to generate the entries feed.", e);
}
} else {
bot.getLogger()
.warn("Unable to generate the entries feed. At least one of the required property is missing.");
}
}
}

View file

@ -1,120 +0,0 @@
/*
* EntriesUtils.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.entries;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Commands;
import net.thauvin.erik.mobibot.Constants;
import net.thauvin.erik.mobibot.Utils;
/**
* The <code>Utils</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-19
* @since 1.0
*/
public final class EntriesUtils {
/**
* Disables the default constructor.
*/
private EntriesUtils() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Builds an entry's comment for display on the channel.
*
* @param entryIndex The entry's index.
* @param commentIndex The comment's index.
* @param comment The {@link EntryComment comment} object.
* @return The entry's comment.
*/
public static String buildComment(final int entryIndex, final int commentIndex, final EntryComment comment) {
return (Commands.LINK_CMD + (entryIndex + 1) + '.' + (commentIndex + 1) + ": [" + comment.getNick() + "] "
+ comment.getComment());
}
/**
* Builds an entry's link for display on the channel.
*
* @param index The entry's index.
* @param entry The {@link EntryLink entry} object.
* @return The entry's link.
* @see #buildLink(int, EntryLink, boolean)
*/
public static String buildLink(final int index, final EntryLink entry) {
return buildLink(index, entry, false);
}
/**
* Builds an entry's link for display on the channel.
*
* @param index The entry's index.
* @param entry The {@link EntryLink entry} object.
* @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.")
public static String buildLink(final int index, final EntryLink entry, final boolean isView) {
final StringBuilder buff = new StringBuilder().append(Commands.LINK_CMD).append(index + 1)
.append(": ").append('[').append(entry.getNick()).append(']');
if (isView && entry.hasComments()) {
buff.append("[+").append(entry.getCommentsCount()).append(']');
}
buff.append(' ');
if (Constants.NO_TITLE.equals(entry.getTitle())) {
buff.append(entry.getTitle());
} else {
buff.append(Utils.bold(entry.getTitle()));
}
buff.append(" ( ").append(Utils.green(entry.getLink())).append(" )");
return buff.toString();
}
/**
* Build an entry's tags/categories for display on the channel.
*
* @param entryIndex The entry's index.
* @param entry The {@link EntryLink entry} object.
* @return The entry's tags.
*/
public static String buildTags(final int entryIndex, final EntryLink entry) {
return (Commands.LINK_CMD + (entryIndex + 1) + "T: " + entry.getPinboardTags().replace(",", ", "));
}
}

View file

@ -1,121 +0,0 @@
/*
* EntryComment.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.entries;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* The class used to store comments associated to a specific entry.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Jan 31, 2004
* @since 1.0
*/
@SuppressWarnings({"PMD.DataClass"})
public class EntryComment implements Serializable {
// Serial version UID
static final long serialVersionUID = 1L;
// Creation date
private final LocalDateTime date = LocalDateTime.now();
private String comment = "";
private String nick = "";
/**
* Creates a new comment.
*
* @param comment The new comment.
* @param nick The nickname of the comment's author.
*/
public EntryComment(final String comment, final String nick) {
this.comment = comment;
this.nick = nick;
}
/**
* Creates a new comment.
*/
@SuppressWarnings("UnusedDeclaration")
protected EntryComment() {
// Required for serialization
}
/**
* Returns the comment.
*
* @return The comment.
*/
public final String getComment() {
return comment;
}
/**
* Sets the comment.
*
* @param comment The actual comment.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setComment(final String comment) {
this.comment = comment;
}
/**
* Returns the comment's creation date.
*
* @return The date.
*/
@SuppressWarnings("UnusedDeclaration")
public final LocalDateTime getDate() {
return date;
}
/**
* Returns the nickname of the author of the comment.
*
* @return The nickname.
*/
public final String getNick() {
return nick;
}
/**
* Sets the nickname of the author of the comment.
*
* @param nick The new nickname.
*/
public final void setNick(final String nick) {
this.nick = nick;
}
}

View file

@ -1,406 +0,0 @@
/*
* EntryLink.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.entries;
import com.rometools.rome.feed.synd.SyndCategory;
import com.rometools.rome.feed.synd.SyndCategoryImpl;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Constants;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The class used to store link entries.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Jan 31, 2004
* @since 1.0
*/
public class EntryLink implements Serializable {
// Serial version UID
static final long serialVersionUID = 1L;
// Link's comments
private final List<EntryComment> comments = new CopyOnWriteArrayList<>();
// Tags/categories
private final List<SyndCategory> tags = new CopyOnWriteArrayList<>();
// Channel
private String channel;
// Creation date
private Date date = Calendar.getInstance().getTime();
// Link's URL
private String link;
// Author's login
private String login = "";
// Author's nickname
private String nick;
// Link's title
private String title;
/**
* Creates a new entry.
*
* @param link The new entry's link.
* @param title The new entry's title.
* @param nick The nickname of the author of the link.
* @param login The login of the author of the link.
* @param channel The channel.
* @param tags The entry's tags/categories.
*/
public EntryLink(final String link,
final String title,
final String nick,
final String login,
final String channel,
final String tags) {
this.link = link;
this.title = title;
this.nick = nick;
this.login = login;
this.channel = channel;
setTags(tags);
}
/**
* Creates a new entry.
*
* @param link The new entry's link.
* @param title The new entry's title.
* @param nick The nickname of the author of the link.
* @param channel The channel.
* @param date The entry date.
* @param tags The entry's tags/categories.
*/
public EntryLink(final String link,
final String title,
final String nick,
final String channel,
final Date date,
final List<SyndCategory> tags) {
this.link = link;
this.title = title;
this.nick = nick;
this.channel = channel;
this.date = new Date(date.getTime());
setTags(tags);
}
/**
* Adds a new comment.
*
* @param comment The actual comment.
* @param nick The nickname of the author of the comment.
* @return The total number of comments for this entry.
*/
public final int addComment(final String comment, final String nick) {
comments.add(new EntryComment(comment, nick));
return (comments.size() - 1);
}
/**
* Deletes a specific comment.
*
* @param index The index of the comment to delete.
*/
public final void deleteComment(final int index) {
if (index < comments.size()) {
comments.remove(index);
}
}
/**
* Returns the channel the link was posted on.
*
* @return The channel
*/
public final String getChannel() {
return channel;
}
/**
* Sets the channel.
*
* @param channel The channel.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setChannel(final String channel) {
this.channel = channel;
}
/**
* Returns a comment.
*
* @param index The comment's index.
* @return The specific comment.
*/
public final EntryComment getComment(final int index) {
return (comments.get(index));
}
/**
* Returns all the comments.
*
* @return The comments.
*/
public final EntryComment[] getComments() {
return (comments.toArray(new EntryComment[0]));
}
/**
* Returns the total number of comments.
*
* @return The count of comments.
*/
public final int getCommentsCount() {
return comments.size();
}
/**
* Returns the comment's creation date.
*
* @return The date.
*/
public final Date getDate() {
return new Date(date.getTime());
}
/**
* Returns the comment's link.
*
* @return The link.
*/
public final String getLink() {
return link;
}
/**
* Sets the comment's link.
*
* @param link The new link.
*/
public final void setLink(final String link) {
this.link = link;
}
/**
* Returns the comment's author login.
*
* @return The login;
*/
public final String getLogin() {
return login;
}
/**
* Sets the comment's author login.
*
* @param login The new login.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setLogin(final String login) {
this.login = login;
}
/**
* Returns the comment's author nickname.
*
* @return The nickname.
*/
public final String getNick() {
return nick;
}
/**
* Sets the comment's author nickname.
*
* @param nick The new nickname.
*/
public final void setNick(final String nick) {
this.nick = nick;
}
/**
* Returns the tags formatted for pinboard.in
*
* @return The tags as a comma-delimited string.
*/
public final String getPinboardTags() {
final StringBuilder pinboardTags = new StringBuilder(nick);
for (final SyndCategory tag : tags) {
pinboardTags.append(',');
pinboardTags.append(tag.getName());
}
return pinboardTags.toString();
}
/**
* Returns the tags.
*
* @return The tags.
*/
public final List<SyndCategory> getTags() {
return tags;
}
/**
* Sets the tags.
*
* @param tags The space-delimited tags.
*/
@SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public final void setTags(final String tags) {
if (tags != null) {
final String[] parts = tags.replace(", ", " ").replace(',', ' ').split(" ");
SyndCategoryImpl tag;
String part;
char mod;
for (final String rawPart : parts) {
part = rawPart.trim();
if (part.length() >= 2) {
tag = new SyndCategoryImpl();
tag.setName(part.substring(1).toLowerCase(Constants.LOCALE));
mod = part.charAt(0);
if (mod == '-') {
// Don't remove the channel tag, if any
if (!channel.substring(1).equals(tag.getName())) {
this.tags.remove(tag);
}
} else if (mod == '+') {
if (!this.tags.contains(tag)) {
this.tags.add(tag);
}
} else {
tag.setName(part.trim().toLowerCase(Constants.LOCALE));
if (!this.tags.contains(tag)) {
this.tags.add(tag);
}
}
}
}
}
}
/**
* Sets the tags.
*
* @param tags The tags.
*/
final void setTags(final List<SyndCategory> tags) {
this.tags.addAll(tags);
}
/**
* Returns the comment's title.
*
* @return The title.
*/
public final String getTitle() {
return title;
}
/**
* Sets the comment's title.
*
* @param title The new title.
*/
public final void setTitle(final String title) {
this.title = title;
}
/**
* Returns true if the entry has comments.
*
* @return true if there are comments, false otherwise.
*/
public final boolean hasComments() {
return (!comments.isEmpty());
}
/**
* Returns true if the entry has tags.
*
* @return true if there are tags, false otherwise.
*/
public final boolean hasTags() {
return (!tags.isEmpty());
}
/**
* /** Sets a comment.
*
* @param index The comment's index.
* @param comment The actual comment.
* @param nick The nickname of the author of the comment.
*/
public final void setComment(final int index, final String comment, final String nick) {
if (index < comments.size()) {
comments.set(index, new EntryComment(comment, nick));
}
}
/**
* Returns a string representation of the object.
*
* @return A string representation of the object.
*/
@Override
public final String toString() {
return super.toString() + "[ channel -> '" + channel + '\'' + ", comments -> " + comments + ", date -> " + date
+ ", link -> '" + link + '\'' + ", login -> '" + login + '\'' + ", nick -> '" + nick + '\''
+ ", tags -> " + tags + ", title -> '" + title + '\'' + " ]";
}
}

View file

@ -1,158 +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 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 List<String> commands = new ArrayList<>();
final Map<String, String> properties = new ConcurrentHashMap<>();
/**
* Responds to a command.
*
* @param bot The bot's instance.
* @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 Mobibot bot,
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 bot The bot's instance.
* @param sender The sender.
* @param args The help arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/
public abstract void helpResponse(final Mobibot bot,
final String sender,
final String args,
@SuppressWarnings("unused") final boolean isPrivate);
/**
* 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

@ -1,105 +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 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() {
super();
commands.add(CALC_CMD);
}
/**
* 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(" ", "") + " = " + 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 Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (StringUtils.isNotBlank(args)) {
bot.send(calc(args));
} else {
helpResponse(bot, sender, args, isPrivate);
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To solve a mathematical calculation:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + CALC_CMD + " <calculation>"));
}
}

View file

@ -1,241 +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.NoticeMessage;
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.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
/**
* 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 {
/**
* The rates keyword.
*/
static final String CURRENCY_RATES_KEYWORD = "rates";
// Currency command
private static final String CURRENCY_CMD = "currency";
// 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() {
super();
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.
* @throws ModuleException If an error occurs while converting.
*/
static Message convertCurrency(final String query) 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();
Element cube;
for (final Element rawCube : cubes) {
cube = rawCube;
EXCHANGE_RATES.put(
cube.getAttribute("currency").getValue(),
cube.getAttribute("rate").getValue());
}
EXCHANGE_RATES.put("EUR", "1");
} catch (JDOMException e) {
throw new ModuleException(query, "An error has occurred while parsing the exchange rates table.", e);
} catch (IOException e) {
throw new ModuleException(
query, "An error has occurred while fetching the exchange rates table.", e);
}
}
if (EXCHANGE_RATES.isEmpty()) {
return new ErrorMessage("Sorry, but the exchange rate table is empty.");
} else {
final String[] cmds = query.split(" ");
if (cmds.length == 4) {
if (cmds[3].equals(cmds[1]) || "0".equals(cmds[0])) {
return new ErrorMessage("You're kidding, right?");
} else {
try {
final double amt = Double.parseDouble(cmds[0].replace(",", ""));
final double from =
Double.parseDouble(EXCHANGE_RATES.get(cmds[1].toUpperCase(Constants.LOCALE)));
final double to = Double.parseDouble(EXCHANGE_RATES.get(cmds[3].toUpperCase(Constants.LOCALE)));
return new PublicMessage(
NumberFormat.getCurrencyInstance(Locale.US).format(amt).substring(1)
+ ' '
+ cmds[1].toUpperCase(Constants.LOCALE)
+ " = "
+ NumberFormat.getCurrencyInstance(Locale.US)
.format((amt * to) / from)
.substring(1)
+ ' '
+ cmds[3].toUpperCase(Constants.LOCALE));
} catch (Exception e) {
throw new ModuleException("convertCurrency(" + query + ')',
"The supported currencies are: " + EXCHANGE_RATES.keySet(), e);
}
}
} else if (CURRENCY_RATES_KEYWORD.equals(query)) {
final StringBuilder buff = new StringBuilder().append('[').append(pubDate).append("]: ");
int i = 0;
for (final Map.Entry<String, String> rate : EXCHANGE_RATES.entrySet()) {
if (i > 0) {
buff.append(", ");
}
buff.append(rate.getKey()).append(": ").append(rate.getValue());
i++;
}
return new NoticeMessage(buff.toString());
}
}
return new ErrorMessage("The supported currencies are: " + EXCHANGE_RATES.keySet());
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
synchronized (this) {
if (!pubDate.equals(Utils.today())) {
EXCHANGE_RATES.clear();
}
}
super.commandResponse(bot, sender, cmd, args, isPrivate);
}
/**
* Converts the specified currencies.
*/
@SuppressFBWarnings("REDOS")
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String query) {
if (StringUtils.isNotBlank(sender) && StringUtils.isNotBlank(query)) {
if (query.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+")) {
try {
final Message msg = convertCurrency(query);
if (msg.isError()) {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, false);
}
bot.send(sender, msg);
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage());
}
} else {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, true);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To convert from one currency to another:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + CURRENCY_CMD + " [100 USD to EUR]"));
if (args.endsWith(CURRENCY_CMD)) {
bot.send(sender, "For a listing of currency rates:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + CURRENCY_CMD) + ' ' + CURRENCY_RATES_KEYWORD);
bot.send(sender, "For a listing of supported currencies:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + CURRENCY_CMD));
}
}
}

View file

@ -1,102 +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;
/**
* 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() {
super();
commands.add(DICE_CMD);
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
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: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of "
+ Utils.bold(playerTotal));
i = r.nextInt(6) + 1;
y = r.nextInt(6) + 1;
final int total = i + y;
bot.action(
"rolled two dice: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of " + Utils.bold(total));
if (playerTotal < total) {
bot.action("wins.");
} else if (playerTotal > total) {
bot.action("lost.");
} else {
bot.action("tied.");
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To roll the dice:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + DICE_CMD));
}
}

View file

@ -1,175 +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.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
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";
// Tab indent (4 spaces)
private static final String TAB_INDENT = " ";
/**
* Creates a new {@link GoogleSearch} instance.
*/
public GoogleSearch() {
super();
commands.add(GOOGLE_CMD);
properties.put(GOOGLE_API_KEY_PROP, "");
properties.put(GOOGLE_CSE_KEY_PROP, "");
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
if (isEnabled()) {
bot.send(sender, "To search Google:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + GOOGLE_CMD + " <query>"));
} else {
bot.send(sender, "The Google search module is disabled.");
}
}
/**
* Searches Google.
*/
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String query) {
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());
}
} else {
helpResponse(bot, sender, query, true);
}
}
/**
* 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 String q = URLEncoder.encode(query, StandardCharsets.UTF_8.toString());
final URL url =
new URL("https://www.googleapis.com/customsearch/v1?key="
+ apiKey
+ "&cx="
+ cseKey
+ "&q="
+ q
+ "&filter=1&num=5&alt=json");
final URLConnection conn = url.openConnection();
final StringBuilder sb = new StringBuilder();
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
final JSONObject json = new JSONObject(sb.toString());
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(TAB_INDENT + j.getString("link"), 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.");
}
}
}

View file

@ -1,132 +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.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
/**
* 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() {
super();
commands.add(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 URLConnection conn = url.openConnection();
final StringBuilder sb = new StringBuilder();
try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
final JSONObject json = new JSONObject(sb.toString());
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 Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
new Thread(() -> run(bot, sender, cmd, args)).start();
}
/**
* Returns a random joke from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a>.
*/
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String arg) {
try {
bot.send(Utils.cyan(randomJoke().getMessage()));
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage());
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To retrieve a random joke:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + JOKE_CMD));
}
}

View file

@ -1,201 +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 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() {
super();
commands.add(LOOKUP_CMD);
}
/**
* 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 Mobibot bot,
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) {
if (bot.getLogger().isDebugEnabled()) {
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(bot, sender, args, true);
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To perform a DNS lookup query:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + 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

@ -1,100 +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 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() {
super();
commands.add(PING_CMD);
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
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())));
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To ping the bot:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + PING_CMD));
}
}

View file

@ -1,229 +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 okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 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() {
super();
commands.add(STOCK_CMD);
properties.put(ALPHAVANTAGE_API_KEY_PROP, "");
}
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "false positive?")
private static JSONObject getJsonResponse(final Response response, final String debugMessage)
throws IOException, ModuleException {
if (response.isSuccessful()) {
if (response.body() != null) {
final JSONObject json = new JSONObject(Objects.requireNonNull(response.body()).string());
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, "Invalid Response (" + response.code() + ')');
}
} 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<>();
final OkHttpClient client = new OkHttpClient();
try {
// Search for symbol/keywords
Request request = new Request.Builder().url(
ALAPHAVANTAGE_URL + "SYMBOL_SEARCH&keywords=" + symbol + "&apikey=" + apiKey).build();
Response response = client.newCall(request).execute();
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
request = new Request.Builder().url(
ALAPHAVANTAGE_URL + "GLOBAL_QUOTE&symbol=" + symbolInfo.getString("1. symbol") + "&apikey="
+ apiKey).build();
response = client.newCall(request).execute();
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);
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To retrieve a stock quote:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + STOCK_CMD + " <symbol|keywords>"));
}
/**
* Returns the specified stock quote from Alpha Advantage.
*/
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String symbol) {
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(bot, sender, symbol, true);
}
}
}

View file

@ -1,158 +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 net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.NoticeMessage;
import twitter4j.DirectMessage;
import twitter4j.Status;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;
/**
* 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 CONSUMER_KEY_PROP = "twitter-consumerKey";
static final String CONSUMER_SECRET_PROP = "twitter-consumerSecret";
static final String TOKEN_PROP = "twitter-token";
static final String TOKEN_SECRET_PROP = "twitter-tokenSecret";
// Twitter command
private static final String TWITTER_CMD = "twitter";
/**
* Creates a new {@link Twitter} instance.
*/
public Twitter() {
super();
commands.add(TWITTER_CMD);
properties.put(CONSUMER_SECRET_PROP, "");
properties.put(CONSUMER_KEY_PROP, "");
properties.put(TOKEN_PROP, "");
properties.put(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);
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
if (isEnabled()) {
bot.send(sender, "To post to Twitter:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + TWITTER_CMD + " <message>"));
} else {
bot.send(sender, "The Twitter posting facility is disabled.");
}
}
/**
* 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);
}
/**
* Posts to twitter.
*/
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String message) {
try {
bot.send(sender,
post(sender, message + " (by " + sender + " on " + bot.getChannel() + ')', false).getMessage());
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage());
}
}
}

View file

@ -1,7 +1,7 @@
/*
* War.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -32,77 +32,82 @@
package net.thauvin.erik.mobibot.modules;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.jetbrains.annotations.NotNull;
import org.pircbotx.hooks.types.GenericMessageEvent;
import java.security.SecureRandom;
import static net.thauvin.erik.mobibot.Utils.bold;
/**
* The War module.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-28
* @since 1.0
*/
public final class War extends AbstractModule {
// Random
private static final SecureRandom RANDOM = new SecureRandom();
// War command
private static final String WAR_CMD = "war";
// Deck of card
private static final String[] WAR_DECK =
new String[]{ "Ace", "King", "Queen", "Jack", "10", "9", "8", "7", "6", "5", "4", "3", "2" };
// Suits for the deck of card
private static final String[] WAR_SUITS = new String[]{ "Hearts", "Spades", "Diamonds", "Clubs" };
private static final String[] HEARTS =
{"🂱", "🂾", "🂽", "🂼", "🂻", "🂺", "🂹", "🂸", "🂷", "🂶", "🂵", "🂴", "🂳", "🂲"};
private static final String[] SPADES =
{"🂡", "🂮", "🂭", "🂬", "🂫", "🂪", "🂩", "🂨", "🂧", "🂦", "🂥", "🂤", "🂣", "🂢"};
private static final String[] DIAMONDS =
{"🃁", "🃎", "🃍", "🃌", "🃋", "🃊", "🃉", "🃈", "🃇", "🃆", "🃅", "🃄", "🃃", "🃂"};
private static final String[] CLUBS =
{"🃑", "🃞", "🃝", "🃜", "🃛", "🃚", "🃙", "🃘", "🃗", "🃖", "🃕", "🃔", "🃓", "🃒"};
private static final String[][] DECK = {HEARTS, SPADES, DIAMONDS, CLUBS};
/**
* The default constructor.
*/
public War() {
super();
commands.add(WAR_CMD);
help.add("To play war:");
help.add(Utils.helpFormat("%c " + WAR_CMD));
}
@NotNull
@Override
public String getName() {
return "War";
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom();
public void commandResponse(@NotNull final String channel, @NotNull final String cmd, @NotNull final String args,
@NotNull final GenericMessageEvent event) {
int i;
int y;
while (true) {
i = r.nextInt(WAR_DECK.length);
y = r.nextInt(WAR_DECK.length);
i = RANDOM.nextInt(HEARTS.length);
y = RANDOM.nextInt(HEARTS.length);
bot.send(bot.getChannel(),
sender + " drew the " + Utils.bold(WAR_DECK[i]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]);
bot.action("drew the " + Utils.bold(WAR_DECK[y]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]);
event.respond("you drew " + DECK[RANDOM.nextInt(DECK.length)][i]);
event.getBot().sendIRC().action(channel, "drew " + DECK[RANDOM.nextInt(DECK.length)][y]);
if (i != y) {
break;
}
bot.send("This means " + Utils.bold("WAR") + '!');
event.respond("This means " + bold("WAR") + '!');
}
if (i < y) {
bot.action("lost.");
event.getBot().sendIRC().action(channel, "lost.");
} else {
bot.action("wins.");
event.getBot().sendIRC().action(channel, "wins.");
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To play war:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + WAR_CMD));
}
}

View file

@ -1,244 +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 okhttp3.HttpUrl;
import org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 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() {
super();
commands.add(WEATHER_CMD);
properties.put(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) + " \u00B0F, " + Math.round(c) + " \u00B0C";
}
/**
* 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() + " [" + 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) {
if (condition.indexOf(",") == -1) {
condition.append(StringUtils.capitalize(w.getDescription()));
} else {
condition.append(", ").append(w.getDescription());
}
}
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 {
final HttpUrl url = Objects.requireNonNull(HttpUrl.parse(
"https://openweathermap.org/find"))
.newBuilder()
.addQueryParameter("q",
city + ',' + country)
.build();
messages.add(new NoticeMessage(url.toString(), 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;
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To display weather information:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + WEATHER_CMD + " <city> [, <country code>]"));
bot.send(sender, "For example:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + WEATHER_CMD + " paris, fr"));
bot.send(sender,
"The default ISO 3166 country code is " + Utils.bold("US")
+ ". Zip codes are supported in most countries.");
}
/**
* Fetches the weather data from a specific city.
*/
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String args) {
if (StringUtils.isNotBlank(args)) {
try {
final List<Message> messages = getWeather(args, properties.get(OWM_API_KEY_PROP));
if (messages.get(0).isError()) {
helpResponse(bot, sender, args, true);
} 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(bot, sender, args, true);
}
}
private static String wind(final Double w) {
final double kmh = w * 1.60934;
return Math.round(w) + " mph, " + Math.round(kmh) + " km/h";
}
}

View file

@ -1,249 +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.Constants;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.msg.ErrorMessage;
import net.thauvin.erik.mobibot.msg.Message;
import net.thauvin.erik.mobibot.msg.PublicMessage;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
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() {
super();
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((query.substring(query.indexOf(' ') + 1).trim()
.toUpperCase(Constants.LOCALE))));
final String response;
if (tz != null) {
if (BEATS_KEYWORD.equals(tz)) {
response = ("The current Internet Time is: " + internetTime() + ' ' + BEATS_KEYWORD);
} else {
response = ZonedDateTime.now().withZoneSameInstant(ZoneId.of(tz)).format(
DateTimeFormatter.ofPattern("'The time is 'HH:mm' on 'EEEE, d MMMM yyyy' in '"))
+ tz.substring(tz.indexOf('/') + 1).replace('_', ' ');
}
} else {
return new ErrorMessage("The supported countries/zones are: " + COUNTRIES_MAP.keySet());
}
return new PublicMessage(response);
}
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final Message msg = worldTime(args);
if (isPrivate) {
bot.send(sender, msg.getMessage(), true);
} else {
if (msg.isError()) {
bot.send(sender, msg.getMessage());
} else {
bot.send(msg.getMessage());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void helpResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
bot.send(sender, "To display a country's current date/time:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + TIME_CMD) + " [<country code>]");
bot.send(sender, "For a listing of the supported countries:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + TIME_CMD));
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPrivateMsgEnabled() {
return true;
}
}

View file

@ -1,193 +0,0 @@
/*
* Message.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.msg;
import org.jibble.pircbot.Colors;
/**
* The <code>Message</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class Message {
private String color = Colors.NORMAL;
private boolean isError;
private boolean isNotice;
private boolean isPrivate;
private String msg = "";
/**
* Creates a new message.
*/
public Message() {
// This constructor is intentionally empty
}
/**
* Creates a new message.
*
* @param message The message.
* @param isNotice The notice flag.
* @param isError The error flag.
* @param isPrivate The Private message
*/
@SuppressWarnings("unused")
public Message(final String message, final boolean isNotice, final boolean isError, final boolean isPrivate) {
msg = message;
this.isNotice = isNotice;
this.isError = isError;
this.isPrivate = isPrivate;
}
/**
* Creates a new message.
*
* @param message The message.
* @param isNotice The notice flag.
* @param isError The error flag.
* @param isPrivate The Private message
* @param color The color.
*/
@SuppressWarnings("unused")
public Message(final String message,
final boolean isNotice,
final boolean isError,
final boolean isPrivate,
final String color) {
msg = message;
this.isNotice = isNotice;
this.isError = isError;
this.isPrivate = isPrivate;
this.color = color;
}
/**
* Returns the message color.
*
* @return The color.
*/
public String getColor() {
return color;
}
/**
* Returns the message.
*
* @return The message.
*/
public String getMessage() {
return msg;
}
/**
* Returns the message error flag.
*
* @return The error flag.
*/
public boolean isError() {
return isError;
}
/**
* Returns the message notice flag.
*
* @return The notice flag.
*/
public boolean isNotice() {
return isNotice;
}
/**
* Returns the message private flag.
*
* @return The private flag.
*/
public boolean isPrivate() {
return isPrivate;
}
/**
* Set the color.
*
* @param color The new color.
*/
public void setColor(final String color) {
this.color = color;
}
/**
* Sets the message error flag.
*
* @param error The error flag.
*/
public void setError(final boolean error) {
isError = error;
}
/**
* Sets the message.
*
* @param message The new message.
*/
public void setMessage(final String message) {
msg = message;
}
/**
* Sets the message notice flag.
*
* @param isNotice The notice flag.
*/
public void setNotice(final boolean isNotice) {
this.isNotice = isNotice;
}
/**
* Sets the message private flag.
*
* @param isPrivate The private flag.
*/
@SuppressWarnings("unused")
public void setPrivate(final boolean isPrivate) {
this.isPrivate = isPrivate;
}
@Override
public String toString() {
return "Message{" + "color='" + color + '\'' + ", isError=" + isError + ", isNotice=" + isNotice
+ ", isPrivate=" + isPrivate + ", msg='" + msg + '\'' + '}';
}
}

View file

@ -1,349 +0,0 @@
/*
* Tell.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.tell;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Commands;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The <code>Tell</code> command.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2016-07-02
* @since 1.0
*/
public class Tell {
/**
* The tell command.
*/
public static final String TELL_CMD = "tell";
// Default maximum number of days to keep messages
private static final int DEFAULT_TELL_MAX_DAYS = 7;
// Default message max queue size
private static final int DEFAULT_TELL_MAX_SIZE = 50;
// Serialized object file extension
private static final String SER_EXT = ".ser";
// All keyword
private static final String TELL_ALL_KEYWORD = "all";
//T he delete command.
private static final String TELL_DEL_KEYWORD = "del";
// Bot instance
private final Mobibot bot;
// Maximum number of days to keep messages
private final int maxDays;
// Message maximum queue size
private final int maxSize;
// Messages queue
private final List<TellMessage> messages = new CopyOnWriteArrayList<>();
// Serialized object file
private final String serializedObject;
/**
* Creates a new instance.
*
* @param bot The bot.
* @param maxDays Max days.
* @param maxSize Max size.
*/
public Tell(final Mobibot bot, final String maxDays, final String maxSize) {
this.bot = bot;
this.maxDays = Utils.getIntProperty(maxDays, DEFAULT_TELL_MAX_DAYS);
this.maxSize = Utils.getIntProperty(maxSize, DEFAULT_TELL_MAX_SIZE);
// Load the message queue
serializedObject = bot.getLogsDir() + bot.getName() + SER_EXT;
messages.addAll(TellMessagesMgr.load(serializedObject, bot.getLogger()));
if (clean()) {
save();
}
}
/**
* Cleans the messages queue.
*
* @return <code>true</code> if the queue was cleaned.
*/
@SuppressWarnings("WeakerAccess")
final boolean clean() {
if (bot.getLogger().isDebugEnabled()) {
bot.getLogger().debug("Cleaning the messages.");
}
return TellMessagesMgr.clean(messages, maxDays);
}
/**
* Responds with help.
*
* @param sender The sender.
*/
public void helpResponse(final String sender) {
bot.send(sender, "To send a message to someone when they join the channel:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + TELL_CMD + " <nick> <message>"));
bot.send(sender, "To view queued and sent messages:");
bot.send(sender, bot.helpIndent(bot.getNick() + ": " + TELL_CMD + ' ' + Commands.VIEW_CMD));
bot.send(sender, "Messages are kept for " + Utils.bold(maxDays) + Utils.plural(maxDays, " day.", " days."));
}
/**
* Returns <code>true</code> if enabled.
*
* @return <code>true</code> or <code>false</code>
*/
public boolean isEnabled() {
return maxSize > 0 && maxDays > 0;
}
/**
* Processes the commands.
*
* @param sender The sender's nick.
* @param cmds The commands string.
*/
@SuppressFBWarnings(value = "CC_CYCLOMATIC_COMPLEXITY", justification = "Working on it.")
public void response(final String sender, final String cmds) {
final String arrow = " --> ";
if (StringUtils.isBlank(cmds)) {
helpResponse(sender);
} else if (cmds.startsWith(Commands.VIEW_CMD)) {
if (bot.isOp(sender) && (Commands.VIEW_CMD + ' ' + TELL_ALL_KEYWORD).equals(cmds)) {
if (!messages.isEmpty()) {
for (final TellMessage message : messages) {
bot.send(sender, Utils.bold(message.getSender()) + arrow + Utils.bold(message.getRecipient())
+ " [ID: " + message.getId() + ", "
+ (message.isReceived() ? "DELIVERED" : "QUEUED") + ']',
true);
}
} else {
bot.send(sender, "There are no messages in the queue.", true);
}
} else {
boolean hasMessage = false;
for (final TellMessage message : messages) {
if (message.isMatch(sender)) {
if (!hasMessage) {
hasMessage = true;
bot.send(sender, "Here are your messages: ", true);
}
if (message.isReceived()) {
bot.send(sender,
Utils.bold(message.getSender()) + arrow + Utils.bold(message.getRecipient())
+ " [" + Utils.utcDateTime(message.getReceived()) + ", ID: "
+ message.getId() + ", DELIVERED]",
true);
} else {
bot.send(sender,
Utils.bold(message.getSender()) + arrow + Utils.bold(message.getRecipient())
+ " [" + Utils.utcDateTime(message.getQueued()) + ", ID: "
+ message.getId() + ", QUEUED]",
true);
}
bot.send(sender, bot.helpIndent(message.getMessage(), false), true);
}
}
if (!hasMessage) {
bot.send(sender, "You have no messages in the queue.", true);
} else {
bot.send(sender, "To delete one or all delivered messages:");
bot.send(sender,
bot.helpIndent(bot.getNick() + ": " + TELL_CMD + ' ' + TELL_DEL_KEYWORD + " <id|"
+ TELL_ALL_KEYWORD + '>'));
bot.send(sender, "Messages are kept for " + Utils.bold(maxDays)
+ Utils.plural(maxDays, " day.", " days."));
}
}
} else if (cmds.startsWith(TELL_DEL_KEYWORD + ' ')) {
final String[] split = cmds.split(" ");
if (split.length == 2) {
final String id = split[1];
boolean deleted = false;
if (TELL_ALL_KEYWORD.equalsIgnoreCase(id)) {
for (final TellMessage message : messages) {
if (message.getSender().equalsIgnoreCase(sender) && message.isReceived()) {
messages.remove(message);
deleted = true;
}
}
if (deleted) {
save();
bot.send(sender, "Delivered messages have been deleted.", true);
} else {
bot.send(sender, "No delivered messages were found.", true);
}
} else {
boolean found = false;
for (final TellMessage message : messages) {
found = message.isMatchId(id);
if (found && (message.getSender().equalsIgnoreCase(sender) || bot.isOp(sender))) {
messages.remove(message);
save();
bot.send(sender, "Your message was deleted from the queue.", true);
deleted = true;
break;
}
}
if (!deleted) {
if (found) {
bot.send(sender, "Only messages that you sent can be deleted.", true);
} else {
bot.send(sender, "The specified message [ID " + id + "] could not be found.", true);
}
}
}
} else {
helpResponse(sender);
}
} else {
final String[] split = cmds.split(" ", 2);
if (split.length == 2 && (StringUtils.isNotBlank(split[1]) && split[1].contains(" "))) {
if (messages.size() < maxSize) {
final TellMessage message = new TellMessage(sender, split[0], split[1].trim());
messages.add(message);
save();
bot.send(sender, "Message [ID " + message.getId() + "] was queued for "
+ Utils.bold(message.getRecipient()), true);
} else {
bot.send(sender, "Sorry, the messages queue is currently full.", true);
}
} else {
helpResponse(sender);
}
}
if (clean()) {
save();
}
}
/**
* Saves the messages queue.
*/
@SuppressWarnings("WeakerAccess")
final void save() {
TellMessagesMgr.save(serializedObject, messages, bot.getLogger());
}
/**
* Checks and sends messages.
*
* @param nickname The user's nickname.
* @param isMessage The message flag.
*/
public void send(final String nickname, final boolean isMessage) {
if (!nickname.equals(bot.getNick()) && isEnabled()) {
messages.stream().filter(message -> message.isMatch(nickname)).forEach(
message -> {
if (message.getRecipient().equalsIgnoreCase(nickname) && !message.isReceived()) {
if (message.getSender().equals(nickname)) {
if (!isMessage) {
bot.send(nickname, Utils.bold("You") + " wanted me to remind you: "
+ Utils.reverseColor(message.getMessage()),
true);
message.setIsReceived();
message.setIsNotified();
save();
}
} else {
bot.send(nickname, message.getSender() + " wanted me to tell you: "
+ Utils.reverseColor(message.getMessage()),
true);
message.setIsReceived();
save();
}
} else if (message.getSender().equalsIgnoreCase(nickname) && message.isReceived()
&& !message.isNotified()) {
bot.send(nickname,
"Your message "
+ Utils.reverseColor("[ID " + message.getId() + ']') + " was sent to "
+ Utils.bold(message.getRecipient()) + " on "
+ Utils.utcDateTime(message.getReceived()),
true);
message.setIsNotified();
save();
}
});
}
}
/**
* Checks and sends messages.
*
* @param nickname The user's nickname.
*/
public void send(final String nickname) {
send(nickname, false);
}
/**
* Returns the messages queue size.
*
* @return The size.
*/
public int size() {
return messages.size();
}
}

View file

@ -1,181 +0,0 @@
/*
* TellMessage.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.tell;
import java.io.Serializable;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* The <code>TellMessage</code> class.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-24
* @since 1.0
*/
@SuppressWarnings("PMD.DataClass")
public class TellMessage implements Serializable {
private static final long serialVersionUID = 2L;
private final String id;
private final String message;
private final LocalDateTime queued;
private final String recipient;
private final String sender;
private boolean isNotified;
private boolean isReceived;
private LocalDateTime received;
/**
* Create a new message.
*
* @param sender The sender's nick.
* @param recipient The recipient's nick.
* @param message The message.
*/
TellMessage(final String sender, final String recipient, final String message) {
this.sender = sender;
this.recipient = recipient;
this.message = message;
this.queued = LocalDateTime.now(Clock.systemUTC());
this.id = this.queued.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
/**
* Returns the message id.
*
* @return The message id.
*/
public String getId() {
return id;
}
/**
* Returns the message text.
*
* @return The text of the message.
*/
public String getMessage() {
return message;
}
/**
* Returns the queued date/time.
*
* @return <code>true</code> if the message is queued.
*/
LocalDateTime getQueued() {
return queued;
}
/**
* Returns the state of the received flag.
*
* @return <code>true</code> if the message has been received.
*/
public LocalDateTime getReceived() {
return received;
}
/**
* Returns the message's recipient.
*
* @return The recipient of the message.
*/
String getRecipient() {
return recipient;
}
/**
* Returns the message's sender.
*
* @return The sender of the message.
*/
public String getSender() {
return sender;
}
/**
* Matches the message sender or recipient.
*
* @param nick The nickname to match with.
* @return <code>true</code> if the nickname matches.
*/
boolean isMatch(final String nick) {
return (sender.equalsIgnoreCase(nick) || recipient.equalsIgnoreCase(nick));
}
/**
* Match the message ID.
*
* @param id The ID to match with.
* @return <code>true</code> if the id matches.
*/
boolean isMatchId(final String id) {
return this.id.equals(id);
}
/**
* Returns the notification flag state.
*
* @return <code>true</code> if the sender has been notified.
*/
boolean isNotified() {
return isNotified;
}
/**
* Returns the received flag state.
*
* @return <code>true</code> if the message was received.
*/
public boolean isReceived() {
return isReceived;
}
/**
* Sets the notified flag.
*/
void setIsNotified() {
isNotified = true;
}
/**
* Sets the received flag.
*/
void setIsReceived() {
received = LocalDateTime.now(Clock.systemUTC());
isReceived = true;
}
}

View file

@ -1,134 +0,0 @@
/*
* TellMessagesMgr.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.tell;
import org.apache.logging.log4j.Logger;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* The Tell Messages Manager.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-26
* @since 1.0
*/
final class TellMessagesMgr {
/**
* Disables the default constructor.
*
* @throws UnsupportedOperationException If the constructor is called.
*/
private TellMessagesMgr() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Cleans the messages queue.
*
* @param tellMessages The messages list.
* @param tellMaxDays The maximum number of days to keep messages for.
* @return <code>True</code> if the queue was cleaned.
*/
static boolean clean(final List<TellMessage> tellMessages, final int tellMaxDays) {
final LocalDateTime today = LocalDateTime.now(Clock.systemUTC());
return tellMessages.removeIf(o -> o.getQueued().plusDays(tellMaxDays).isBefore(today));
}
/**
* Loads the messages.
*
* @param file The serialized objects file.
* @param logger The logger.
* @return The {@link TellMessage} array.
*/
@SuppressWarnings("unchecked")
public static List<TellMessage> load(final String file, final Logger logger) {
try {
try (final ObjectInput input = new ObjectInputStream(
new BufferedInputStream(Files.newInputStream(Paths.get(file))))) {
if (logger.isDebugEnabled()) {
logger.debug("Loading the messages.");
}
return ((List<TellMessage>) input.readObject());
}
} catch (FileNotFoundException ignore) {
// Do nothing
} catch (IOException e) {
logger.error("An IO error occurred loading the messages queue.", e);
} catch (Exception e) {
logger.error("An error occurred loading the messages queue.", e);
}
return new ArrayList<>();
}
/**
* Saves the messages.
*
* @param file The serialized objects file.
* @param messages The {@link TellMessage} array.
* @param logger The logger.
*/
public static void save(final String file, final List<TellMessage> messages, final Logger logger) {
try {
try (final ObjectOutput output = new ObjectOutputStream(
new BufferedOutputStream(Files.newOutputStream(Paths.get(file))))) {
if (logger.isDebugEnabled()) {
logger.debug("Saving the messages.");
}
output.writeObject(messages);
}
} catch (IOException e) {
logger.error("Unable to save messages queue.", e);
}
}
}

View file

@ -0,0 +1,164 @@
/*
* Addons.kt
*
* Copyright (c) 2004-2022, 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 net.thauvin.erik.mobibot.Utils.notContains
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.links.LinksMgr
import net.thauvin.erik.mobibot.modules.AbstractModule
import org.pircbotx.hooks.events.PrivateMessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
import java.util.Properties
/**
* Modules and Commands addons.
*/
class Addons(private val props: Properties) {
private val disabledModules = props.getProperty("disabled-modules", "").split(LinksMgr.TAG_MATCH.toRegex())
private val disableCommands = props.getProperty("disabled-commands", "").split(LinksMgr.TAG_MATCH.toRegex())
val commands: MutableList<AbstractCommand> = mutableListOf()
val modules: MutableList<AbstractModule> = mutableListOf()
val names = Names
/**
* Add a module with properties.
*/
fun add(module: AbstractModule) {
with(module) {
if (disabledModules.notContains(name, true)) {
if (hasProperties()) {
propertyKeys.forEach {
setProperty(it, props.getProperty(it, ""))
}
}
if (isEnabled) {
modules.add(this)
names.modules.add(name)
names.commands.addAll(commands)
}
}
}
}
/**
* Add a command with properties.
*/
fun add(command: AbstractCommand) {
with(command) {
if (disableCommands.notContains(name, true)) {
if (properties.isNotEmpty()) {
properties.keys.forEach {
setProperty(it, props.getProperty(it, ""))
}
}
if (isEnabled()) {
commands.add(this)
if (isVisible) {
if (isOpOnly) {
names.ops.add(name)
} else {
names.commands.add(name)
}
}
}
}
}
}
/**
* Execute a command or module.
*/
fun exec(channel: String, cmd: String, args: String, event: GenericMessageEvent): Boolean {
val cmds = if (event is PrivateMessageEvent) commands else commands.filter { it.isPublic }
for (command in cmds) {
if (command.name.startsWith(cmd)) {
command.commandResponse(channel, args, event)
return true
}
}
val mods = if (event is PrivateMessageEvent) modules.filter { it.isPrivateMsgEnabled } else modules
for (module in mods) {
if (module.commands.contains(cmd)) {
module.commandResponse(channel, cmd, args, event)
return true
}
}
return false
}
/**
* Match a command.
*/
fun match(channel: String, event: GenericMessageEvent): Boolean {
for (command in commands) {
if (command.matches(event.message)) {
command.commandResponse(channel, event.message, event)
return true
}
}
return false
}
/**
* Commands and Modules help.
*/
fun help(channel: String, topic: String, event: GenericMessageEvent): Boolean {
for (command in commands) {
if (command.isVisible && command.name.startsWith(topic)) {
return command.helpResponse(channel, topic, event)
}
}
for (module in modules) {
if (module.commands.contains(topic)) {
return module.helpResponse(event)
}
}
return false
}
/**
* Holds commands and modules names.
*/
object Names {
val modules: MutableList<String> = mutableListOf()
val commands: MutableList<String> = mutableListOf()
val ops: MutableList<String> = mutableListOf()
fun sort() {
modules.sort()
commands.sort()
ops.sort()
}
}
}

View file

@ -1,7 +1,7 @@
/*
* Constants.java
* Constants.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,48 +29,69 @@
* 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 java.util.Locale;
package net.thauvin.erik.mobibot
/**
* 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`.
*/
public final class Constants {
object Constants {
/**
* The connect/read timeout in ms.
*/
public static final int CONNECT_TIMEOUT = 5000;
const val CONNECT_TIMEOUT = 5000
/**
* Default locale.
* Debug command line argument.
*/
public static final Locale LOCALE = Locale.getDefault();
const val DEBUG_ARG = "debug"
/**
* Default IRC Port.
*/
const val DEFAULT_PORT = 6667
/**
* Default IRC Server.
*/
const val DEFAULT_SERVER = "irc.libera.chat"
/**
* Help command line argument.
*/
const val HELP_ARG = "help"
/**
* The help command.
*/
const val HELP_CMD = "help"
/**
* The link command.
*/
const val LINK_CMD = "L"
/**
* The empty title string.
*/
public static final String NO_TITLE = "No Title";
const val NO_TITLE = "No Title"
/**
* Properties command line argument.
*/
const val PROPS_ARG = "properties"
/**
* The tag command
*/
const val TAG_CMD = "T"
/**
* The timer delay in minutes.
*/
public static final long TIMER_DELAY = 10L;
/**
* The twitter post flag property key.
*/
public static final String TWITTER_AUTOPOST_PROP = "twitter-auto-post";
/**
* The Twitter handle property key.
*/
public static final String TWITTER_HANDLE_PROP = "twitter-handle";
const val TIMER_DELAY = 10L
/**
* Disables the default constructor.
* Properties version line argument.
*/
private Constants() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
const val VERSION_ARG = "version"
}

View file

@ -0,0 +1,93 @@
/*
* FeedReader.kt
*
* Copyright (c) 2004-2022, 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.FeedException
import com.rometools.rome.io.SyndFeedInput
import com.rometools.rome.io.XmlReader
import net.thauvin.erik.mobibot.Utils.green
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.entries.FeedsMgr
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import org.pircbotx.hooks.types.GenericMessageEvent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.IOException
import java.net.URL
/**
* Reads an RSS feed.
*/
class FeedReader(private val url: String, val event: GenericMessageEvent) : Runnable {
private val logger: Logger = LoggerFactory.getLogger(FeedsMgr::class.java)
/**
* Fetches the Feed's items.
*/
override fun run() {
try {
readFeed(url).forEach {
event.sendMessage("", it)
}
} catch (e: FeedException) {
if (logger.isWarnEnabled) logger.warn("Unable to parse the feed at $url", e)
event.sendMessage("An error has occurred while parsing the feed: ${e.message}")
} catch (e: IOException) {
if (logger.isWarnEnabled) logger.warn("Unable to fetch the feed at $url", e)
event.sendMessage("An error has occurred while fetching the feed: ${e.message}")
}
}
companion object {
@JvmStatic
@Throws(FeedException::class, IOException::class)
fun readFeed(url: String, maxItems: Int = 5): List<Message> {
val messages = mutableListOf<Message>()
val input = SyndFeedInput()
XmlReader(URL(url)).use { reader ->
val feed = input.build(reader)
val items = feed.entries
if (items.isEmpty()) {
messages.add(NoticeMessage("There is currently nothing to view."))
} else {
items.take(maxItems).forEach {
messages.add(NoticeMessage(it.title))
messages.add(NoticeMessage(helpFormat(it.link.green(), false)))
}
}
}
return messages
}
}
}

View file

@ -0,0 +1,441 @@
/*
* Mobibot.kt
*
* Copyright (c) 2004-2022, 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 net.thauvin.erik.mobibot.Utils.appendIfMissing
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.getIntProperty
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.lastOrEmpty
import net.thauvin.erik.mobibot.Utils.sendList
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
import net.thauvin.erik.mobibot.commands.ChannelFeed
import net.thauvin.erik.mobibot.commands.Cycle
import net.thauvin.erik.mobibot.commands.Die
import net.thauvin.erik.mobibot.commands.Ignore
import net.thauvin.erik.mobibot.commands.Info
import net.thauvin.erik.mobibot.commands.Me
import net.thauvin.erik.mobibot.commands.Modules
import net.thauvin.erik.mobibot.commands.Msg
import net.thauvin.erik.mobibot.commands.Nick
import net.thauvin.erik.mobibot.commands.Recap
import net.thauvin.erik.mobibot.commands.Recap.Companion.storeRecap
import net.thauvin.erik.mobibot.commands.Say
import net.thauvin.erik.mobibot.commands.Users
import net.thauvin.erik.mobibot.commands.Versions
import net.thauvin.erik.mobibot.commands.links.Comment
import net.thauvin.erik.mobibot.commands.links.LinksMgr
import net.thauvin.erik.mobibot.commands.links.Posting
import net.thauvin.erik.mobibot.commands.links.Tags
import net.thauvin.erik.mobibot.commands.links.View
import net.thauvin.erik.mobibot.commands.tell.Tell
import net.thauvin.erik.mobibot.modules.Calc
import net.thauvin.erik.mobibot.modules.CryptoPrices
import net.thauvin.erik.mobibot.modules.CurrencyConverter
import net.thauvin.erik.mobibot.modules.Dice
import net.thauvin.erik.mobibot.modules.GoogleSearch
import net.thauvin.erik.mobibot.modules.Joke
import net.thauvin.erik.mobibot.modules.Lookup
import net.thauvin.erik.mobibot.modules.Ping
import net.thauvin.erik.mobibot.modules.RockPaperScissors
import net.thauvin.erik.mobibot.modules.StockQuote
import net.thauvin.erik.mobibot.modules.War
import net.thauvin.erik.mobibot.modules.Weather2
import net.thauvin.erik.mobibot.modules.WorldTime
import net.thauvin.erik.semver.Version
import org.apache.commons.cli.CommandLine
import org.apache.commons.cli.CommandLineParser
import org.apache.commons.cli.DefaultParser
import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Option
import org.apache.commons.cli.Options
import org.apache.commons.cli.ParseException
import org.pircbotx.Configuration
import org.pircbotx.PircBotX
import org.pircbotx.hooks.ListenerAdapter
import org.pircbotx.hooks.events.ActionEvent
import org.pircbotx.hooks.events.DisconnectEvent
import org.pircbotx.hooks.events.JoinEvent
import org.pircbotx.hooks.events.MessageEvent
import org.pircbotx.hooks.events.NickChangeEvent
import org.pircbotx.hooks.events.PartEvent
import org.pircbotx.hooks.events.PrivateMessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.Paths
import java.util.Properties
import java.util.regex.Pattern
import kotlin.system.exitProcess
@Version(properties = "version.properties", className = "ReleaseInfo", template = "version.mustache", type = "kt")
class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Properties) : ListenerAdapter() {
// The bot configuration.
private val config: Configuration
// Commands and Modules
private val addons: Addons
// Tell module
private val tell: Tell
/** Logger. */
val logger: Logger = LoggerFactory.getLogger(Mobibot::class.java)
/**
* Connects to the server and joins the channel.
*/
fun connect() {
PircBotX(config).startBot()
}
/**
* Responds with the default help.
*/
private fun helpDefault(event: GenericMessageEvent) {
event.sendMessage("Type a URL on $channel to post it.")
event.sendMessage("For more information on a specific command, type:")
event.sendMessage(
Utils.helpFormat(
Utils.buildCmdSyntax(
"%c ${Constants.HELP_CMD} <command>",
event.bot().nick,
event is PrivateMessageEvent
)
),
)
event.sendMessage("The commands are:")
event.sendList(addons.names.commands, 8, isBold = true, isIndent = true)
if (isChannelOp(channel, event)) {
event.sendMessage("The op commands are:")
event.sendList(addons.names.ops, 8, isBold = true, isIndent = true)
}
}
/**
* Responds with the default, commands or modules help.
*/
private fun helpResponse(event: GenericMessageEvent, topic: String) {
if (topic.isBlank() || !addons.help(channel, topic.lowercase().trim(), event)) {
helpDefault(event)
}
}
override fun onAction(event: ActionEvent?) {
event?.channel?.let {
if (channel == it.name) {
event.user?.let { user ->
storeRecap(user.nick, event.action, true)
}
}
}
}
override fun onDisconnect(event: DisconnectEvent?) {
event?.let {
with(event.getBot<PircBotX>()) {
LinksMgr.twitter.notification("$nick disconnected from irc://$serverHostname")
}
}
LinksMgr.twitter.shutdown()
}
override fun onPrivateMessage(event: PrivateMessageEvent?) {
event?.user?.let { user ->
if (logger.isTraceEnabled) logger.trace("<<< ${user.nick}: ${event.message}")
val cmds = event.message.trim().split(" ".toRegex(), 2)
val cmd = cmds[0].lowercase()
val args = cmds.lastOrEmpty().trim()
if (cmd.startsWith(Constants.HELP_CMD)) { // help
helpResponse(event, args)
} else if (!addons.exec(channel, cmd, args, event)) { // Execute command or module
helpDefault(event)
}
}
}
override fun onJoin(event: JoinEvent?) {
event?.user?.let { user ->
with(event.getBot<PircBotX>()) {
if (user.nick == nick) {
LinksMgr.twitter.notification("$nick has joined ${event.channel.name} on irc://$serverHostname")
} else {
tell.send(event)
}
}
}
}
override fun onMessage(event: MessageEvent?) {
event?.user?.let { user ->
val sender = user.nick
val message = event.message
tell.send(event)
if (message.matches("(?i)${Pattern.quote(event.bot().nick)}:.*".toRegex())) { // mobibot: <command>
if (logger.isTraceEnabled) logger.trace(">>> $sender: $message")
val cmds = message.substring(message.indexOf(':') + 1).trim().split(" ".toRegex(), 2)
val cmd = cmds[0].lowercase()
val args = cmds.lastOrEmpty().trim()
if (cmd.startsWith(Constants.HELP_CMD)) { // mobibot: help
helpResponse(event, args)
} else {
// Execute module or command
addons.exec(channel, cmd, args, event)
}
} else if (addons.match(channel, event)) { // Links, e.g.: https://www.example.com/ or L1: , etc.
if (logger.isTraceEnabled) logger.trace(">>> $sender: $message")
}
storeRecap(sender, message, false)
}
}
override fun onNickChange(event: NickChangeEvent?) {
event?.let { tell.send(event) }
}
override fun onPart(event: PartEvent?) {
event?.user?.let { user ->
with(event.getBot<PircBotX>()) {
if (user.nick == nick) {
LinksMgr.twitter.notification("$nick has left ${event.channel.name} on irc://$serverHostname")
}
}
}
}
companion object {
@JvmStatic
@Throws(Exception::class)
fun main(args: Array<String>) {
// Set up the command line options
val options = Options()
.addOption(
Constants.HELP_ARG.substring(0, 1),
Constants.HELP_ARG,
false,
"print this help message"
)
.addOption(
Constants.DEBUG_ARG.substring(0, 1), Constants.DEBUG_ARG, false,
"print debug & logging data directly to the console"
)
.addOption(
Option.builder(Constants.PROPS_ARG.substring(0, 1)).hasArg()
.argName("file")
.desc("use alternate properties file")
.longOpt(Constants.PROPS_ARG).build()
)
.addOption(
Constants.VERSION_ARG.substring(0, 1),
Constants.VERSION_ARG,
false,
"print version info"
)
// Parse the command line
val parser: CommandLineParser = DefaultParser()
val commandLine: CommandLine
try {
commandLine = parser.parse(options, args)
} catch (e: ParseException) {
System.err.println("CLI Parsing failed. Reason: ${e.message}")
exitProcess(1)
}
when {
commandLine.hasOption(Constants.HELP_ARG[0]) -> {
// Output the usage
HelpFormatter().printHelp(Mobibot::class.java.name, options)
}
commandLine.hasOption(Constants.VERSION_ARG[0]) -> {
// Output the version
println("${ReleaseInfo.PROJECT} ${ReleaseInfo.VERSION} (${ReleaseInfo.BUILDDATE.toIsoLocalDate()})")
println(ReleaseInfo.WEBSITE)
}
else -> {
// Load the properties
val p = Properties()
try {
Files.newInputStream(
Paths.get(commandLine.getOptionValue(Constants.PROPS_ARG[0], "./mobibot.properties"))
).use { fis ->
p.load(fis)
}
} catch (ignore: FileNotFoundException) {
System.err.println("Unable to find properties file.")
exitProcess(1)
} catch (ignore: IOException) {
System.err.println("Unable to open properties file.")
exitProcess(1)
}
val nickname = p.getProperty("nick", Mobibot::class.java.name.lowercase())
val channel = p.getProperty("channel")
val logsDir = p.getProperty("logs", ".").appendIfMissing(File.separatorChar)
// Redirect stdout and stderr
if (!commandLine.hasOption(Constants.DEBUG_ARG[0])) {
try {
val stdout = PrintStream(
BufferedOutputStream(
FileOutputStream(
logsDir + channel.substring(1) + '.' + Utils.today() + ".log", true
)
), true
)
System.setOut(stdout)
} catch (ignore: IOException) {
System.err.println("Unable to open output (stdout) log file.")
exitProcess(1)
}
try {
val stderr = PrintStream(
BufferedOutputStream(
FileOutputStream("$logsDir$nickname.err", true)
), true
)
System.setErr(stderr)
} catch (ignore: IOException) {
System.err.println("Unable to open error (stderr) log file.")
exitProcess(1)
}
}
// Start the bot
Mobibot(nickname, channel, logsDir, p).connect()
}
}
}
}
/**
* Initialize the bot.
*/
init {
val ircServer = p.getProperty("server", Constants.DEFAULT_SERVER)
config = Configuration.Builder().apply {
name = nickname
login = p.getProperty("login", nickname)
realName = p.getProperty("realname", nickname)
addServer(
ircServer,
p.getIntProperty("port", Constants.DEFAULT_PORT)
)
addAutoJoinChannel(channel)
addListener(this@Mobibot)
version = "${ReleaseInfo.PROJECT} ${ReleaseInfo.VERSION}"
isAutoNickChange = true
val identPwd = p.getProperty("ident")
if (!identPwd.isNullOrBlank()) {
nickservPassword = identPwd
}
val identNick = p.getProperty("ident-nick")
if (!identNick.isNullOrBlank()) {
nickservNick = identNick
}
val identMsg = p.getProperty("ident-msg")
if (!identMsg.isNullOrBlank()) {
nickservCustomMessage = identMsg
}
isAutoReconnect = true
//socketConnectTimeout = Constants.CONNECT_TIMEOUT
//socketTimeout = Constants.CONNECT_TIMEOUT
//messageDelay = StaticDelay(500)
}.buildConfiguration()
// Load the current entries
with(LinksMgr) {
entries.channel = channel
entries.ircServer = ircServer
entries.logsDir = logsDirPath
entries.backlogs = p.getProperty("backlogs", "")
entries.load()
// Set up pinboard
pinboard.setApiToken(p.getProperty("pinboard-api-token", ""))
}
addons = Addons(p)
// Load the commands
addons.add(ChannelFeed(channel.removePrefix("#")))
addons.add(Comment())
addons.add(Cycle())
addons.add(Die())
addons.add(Ignore())
addons.add(LinksMgr())
addons.add(Me())
addons.add(Modules(addons.names.modules))
addons.add(Msg())
addons.add(Nick())
addons.add(Posting())
addons.add(Recap())
addons.add(Say())
addons.add(Tags())
// Tell command
tell = Tell("${logsDirPath}${nickname}.ser")
addons.add(tell)
addons.add(LinksMgr.twitter)
addons.add(Users())
addons.add(Versions())
addons.add(View())
// Load the modules
addons.add(Calc())
addons.add(CryptoPrices())
addons.add(CurrencyConverter())
addons.add(Dice())
addons.add(GoogleSearch())
addons.add(Info(tell))
addons.add(Joke())
addons.add(Lookup())
addons.add(Ping())
addons.add(RockPaperScissors())
addons.add(StockQuote())
addons.add(Weather2())
addons.add(WorldTime())
addons.add(War())
// Sort the addons
addons.names.sort()
}
}

View file

@ -0,0 +1,125 @@
/*
* Pinboard.kt
*
* Copyright (c) 2004-2022, 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 kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.thauvin.erik.mobibot.entries.EntryLink
import net.thauvin.erik.pinboard.PinboardPoster
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Date
/**
* Handles posts to pinboard.in.
*/
class Pinboard {
private val poster = PinboardPoster()
/**
* Adds a pin.
*/
fun addPin(ircServer: String, entry: EntryLink) {
if (poster.apiToken.isNotBlank()) {
runBlocking {
launch {
poster.addPin(
entry.link,
entry.title,
entry.postedBy(ircServer),
entry.pinboardTags,
entry.date.toTimestamp()
)
}
}
}
}
/**
* Sets the pinboard API token.
*/
fun setApiToken(apiToken: String) {
poster.apiToken = apiToken
}
/**
* Deletes a pin.
*/
fun deletePin(entry: EntryLink) {
if (poster.apiToken.isNotBlank()) {
runBlocking {
launch {
poster.deletePin(entry.link)
}
}
}
}
/**
* Updates a pin.
*/
fun updatePin(ircServer: String, oldUrl: String, entry: EntryLink) {
if (poster.apiToken.isNotBlank()) {
runBlocking {
launch {
with(entry) {
if (oldUrl != link) {
poster.deletePin(oldUrl)
}
poster.addPin(link, title, entry.postedBy(ircServer), pinboardTags, date.toTimestamp())
}
}
}
}
}
/**
* Format a date to a UTC timestamp.
*/
private fun Date.toTimestamp(): String {
return ZonedDateTime.ofInstant(
toInstant().truncatedTo(ChronoUnit.SECONDS),
ZoneId.systemDefault()
).format(DateTimeFormatter.ISO_INSTANT)
}
/**
* Returns the pinboard.in extended attribution line.
*/
private fun EntryLink.postedBy(ircServer: String): String {
return "Posted by $nick on $channel ( $ircServer )"
}
}

View file

@ -0,0 +1,112 @@
/*
* TwitterOAuth.kt
*
* Copyright (c) 2004-2022, 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 twitter4j.TwitterException
import twitter4j.TwitterFactory
import twitter4j.auth.AccessToken
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import kotlin.system.exitProcess
/**
* The `TwitterOAuth` class.
*
* Go to [https://developer.twitter.com/en/apps](https://developer.twitter.com/en/apps) to register your bot.
*
* Then execute:
*
* `java -cp mobibot.jar net.thauvin.erik.mobibot.TwitterOAuth <consumerKey> <consumerSecret>`
*
* and follow the prompts/instructions.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net)
* @author [Twitter4J Example](https://twitter4j.org/en/code-examples.html#oauth)
*/
object TwitterOAuth {
/**
* Twitter OAuth Client Registration.
*
* @param args The consumerKey and consumerSecret should be passed as arguments.
*/
@JvmStatic
@Throws(TwitterException::class, IOException::class)
fun main(args: Array<String>) {
if (args.size == 2) {
with(TwitterFactory.getSingleton()) {
setOAuthConsumer(args[0], args[1])
val requestToken = oAuthRequestToken
var accessToken: AccessToken? = null
BufferedReader(InputStreamReader(System.`in`)).use { br ->
while (null == accessToken) {
print(
"""
Open the following URL and grant access to your account:
${requestToken.authorizationURL}
Enter the PIN (if available) or just hit enter. [PIN]: """.trimIndent()
)
val pin = br.readLine()
try {
accessToken = if (!pin.isNullOrEmpty()) {
getOAuthAccessToken(requestToken, pin)
} else {
oAuthAccessToken
}
println(
"""
Please add the following to the bot's property file:
twitter-consumerKey=${args[0]}
twitter-consumerSecret=${args[1]}
twitter-token=${accessToken?.token}
twitter-tokenSecret=${accessToken?.tokenSecret}
""".trimIndent()
)
} catch (te: TwitterException) {
if (401 == te.statusCode) {
println("Unable to get the access token.")
} else {
te.printStackTrace()
}
}
}
}
}
} else {
println("Usage: ${TwitterOAuth::class.java.name} <consumerKey> <consumerSecret>")
}
exitProcess(0)
}
}

View file

@ -1,7 +1,7 @@
/*
* TwitterTimer.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -32,10 +32,11 @@
package net.thauvin.erik.mobibot
import java.util.*
import net.thauvin.erik.mobibot.modules.Twitter
import java.util.TimerTask
class TwitterTimer(var bot: Mobibot, private var index: Int) : TimerTask() {
class TwitterTimer(private var twitter: Twitter, private var index: Int) : TimerTask() {
override fun run() {
bot.twitterAutoPost(index)
twitter.postEntry(index)
}
}

View file

@ -0,0 +1,391 @@
/*
* Utils.kt
*
* Copyright (c) 2004-2022, 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 net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.Message.Companion.DEFAULT_COLOR
import org.jsoup.Jsoup
import org.pircbotx.Colors
import org.pircbotx.PircBotX
import org.pircbotx.hooks.events.PrivateMessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
import java.io.BufferedReader
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.Properties
import java.util.stream.Collectors
import kotlin.time.DurationUnit
import kotlin.time.toDuration
/**
* Miscellaneous utilities.
*/
@Suppress("TooManyFunctions")
object Utils {
private val searchFlags = arrayOf("%c", "%n")
/**
* Appends a suffix to the end of the String if not present.
*/
@JvmStatic
fun String.appendIfMissing(suffix: Char): String {
return if (last() != suffix) {
"$this${suffix}"
} else {
this
}
}
/**
* Makes the given int bold.
*/
@JvmStatic
fun Int.bold(): String = toString().bold()
/**
* Makes the given long bold.
*/
@JvmStatic
fun Long.bold(): String = toString().bold()
/**
* Makes the given string bold.
*/
@JvmStatic
fun String?.bold(): String = colorize(Colors.BOLD)
/**
* Returns the [PircBotX] instance.
*/
fun GenericMessageEvent.bot(): PircBotX {
return getBot() as PircBotX
}
/**
* Build a help command by replacing `%c` with the bot's pub/priv command, and `%n` with the bot's
* nick.
*/
@JvmStatic
fun buildCmdSyntax(text: String, botNick: String, isPrivate: Boolean): String {
val replace = arrayOf(if (isPrivate) "/msg $botNick" else "$botNick:", botNick)
return text.replaceEach(searchFlags, replace)
}
/**
* Capitalize a string.
*/
@JvmStatic
fun String.capitalise(): String = lowercase().replaceFirstChar { it.uppercase() }
/**
* Capitalize words
*/
fun String.capitalizeWords(): String = split(" ").joinToString(" ") { it.capitalise() }
/**
* Colorize a string.
*/
@JvmStatic
fun String?.colorize(color: String): String {
return if (isNullOrEmpty()) {
""
} else if (color == DEFAULT_COLOR) {
this
} else if (Colors.BOLD == color || Colors.REVERSE == color) {
color + this + color
} else {
color + this + Colors.NORMAL
}
}
/**
* Makes the given string cyan.
*/
@JvmStatic
fun String?.cyan(): String = colorize(Colors.CYAN)
/**
* URL encodes the given string.
*/
@JvmStatic
fun encodeUrl(s: String): String = URLEncoder.encode(s, StandardCharsets.UTF_8)
/**
* Returns a property as an int.
*/
@JvmStatic
fun Properties.getIntProperty(key: String, defaultValue: Int): Int {
return getProperty(key)?.toIntOrDefault(defaultValue) ?: defaultValue
}
/**
* Makes the given string green.
*/
@JvmStatic
fun String?.green(): String = colorize(Colors.DARK_GREEN)
/**
* Returns a formatted help string.
*/
@JvmStatic
@JvmOverloads
fun helpFormat(help: String, isBold: Boolean = true, isIndent: Boolean = true): String {
val s = if (isBold) help.bold() else help
return if (isIndent) s.prependIndent() else s
}
/**
* Returns {@code true} if the specified user is an operator on the [channel].
*/
@JvmStatic
fun isChannelOp(channel: String, event: GenericMessageEvent): Boolean {
return event.bot().userChannelDao.getChannel(channel).isOp(event.user)
}
/**
* Returns the last item of a list of strings or empty if none.
*/
@JvmStatic
fun List<String>.lastOrEmpty(): String {
return if (this.size >= 2) {
this.last()
} else
""
}
/**
* Returns {@code true} if the list does not contain the given string.
*/
@JvmStatic
fun List<String>.notContains(text: String, ignoreCase: Boolean = false) = this.none { it.equals(text, ignoreCase) }
/**
* Obfuscates the given string.
*/
@JvmStatic
fun String.obfuscate(): String {
return if (isNotBlank()) {
"x".repeat(length)
} else this
}
/**
* Returns the plural form of a word, if count &gt; 1.
*/
@JvmStatic
fun String.plural(count: Long): String {
return if (count > 1) "${this}s" else this
}
/**
* Makes the given string red.
*/
@JvmStatic
fun String?.red(): String = colorize(Colors.RED)
/**
* Replaces all occurrences of Strings within another String.
*/
@JvmStatic
fun String.replaceEach(search: Array<out String>, replace: Array<out String>): String {
var result = this
if (search.size == replace.size) {
search.forEachIndexed { i, s ->
result = result.replace(s, replace[i])
}
}
return result
}
/**
* Makes the given string reverse color.
*/
@JvmStatic
fun String?.reverseColor(): String = colorize(Colors.REVERSE)
/**
* Send a formatted commands/modules, etc. list.
*/
@JvmStatic
fun GenericMessageEvent.sendList(
list: List<String>,
maxPerLine: Int,
separator: String = " ",
isBold: Boolean = false,
isIndent: Boolean = false
) {
var i = 0
while (i < list.size) {
sendMessage(
helpFormat(
list.subList(i, list.size.coerceAtMost(i + maxPerLine)).joinToString(separator, truncated = ""),
isBold,
isIndent
),
)
i += maxPerLine
}
}
/**
* Sends a [message].
*/
@JvmStatic
fun GenericMessageEvent.sendMessage(channel: String, message: Message) {
if (message.isNotice) {
bot().sendIRC().notice(user.nick, message.msg.colorize(message.color))
} else if (message.isPrivate || this is PrivateMessageEvent || channel.isBlank()) {
respondPrivateMessage(message.msg.colorize(message.color))
} else {
bot().sendIRC().message(channel, message.msg.colorize(message.color))
}
}
/**
* Sends a response as a private message or notice.
*/
@JvmStatic
fun GenericMessageEvent.sendMessage(message: String) {
if (this is PrivateMessageEvent) {
respondPrivateMessage(message)
} else {
bot().sendIRC().notice(user.nick, message)
}
}
/**
* Returns today's date.
*/
@JvmStatic
fun today(): String = LocalDateTime.now().toIsoLocalDate()
/**
* Converts a string to an int.
*/
@JvmStatic
fun String.toIntOrDefault(defaultValue: Int): Int {
return try {
toInt()
} catch (e: NumberFormatException) {
defaultValue
}
}
/**
* Returns the specified date as an ISO local date string.
*/
@JvmStatic
fun Date.toIsoLocalDate(): String {
return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()).toIsoLocalDate()
}
/**
* Returns the specified date as an ISO local date string.
*/
@JvmStatic
fun LocalDateTime.toIsoLocalDate(): String = format(DateTimeFormatter.ISO_LOCAL_DATE)
/**
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
*/
@JvmStatic
fun Date.toUtcDateTime(): String {
return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()).toUtcDateTime()
}
/**
* Returns the specified date formatted as `yyyy-MM-dd HH:mm`.
*/
@JvmStatic
fun LocalDateTime.toUtcDateTime(): String = format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
/**
* Converts XML/XHTML entities to plain text.
*/
@JvmStatic
fun unescapeXml(str: String): String = Jsoup.parse(str).text()
/**
* Converts milliseconds to year month week day hour and minutes.
*/
@JvmStatic
fun uptime(uptime: Long): String {
uptime.toDuration(DurationUnit.MILLISECONDS).toComponents { wholeDays, hours, minutes, _, _ ->
val years = wholeDays / 365
var days = wholeDays % 365
val months = days / 30
days %= 30
val weeks = days / 7
days %= 7
with(StringBuffer()) {
if (years > 0) {
append(years).append(" year".plural(years)).append(' ')
}
if (months > 0) {
append(weeks).append(" month".plural(months)).append(' ')
}
if (weeks > 0) {
append(weeks).append(" week".plural(weeks)).append(' ')
}
if (days > 0) {
append(days).append(" day".plural(days)).append(' ')
}
if (hours > 0) {
append(hours).append(" hour".plural(hours.toLong())).append(' ')
}
append(minutes).append(" minute".plural(minutes.toLong()))
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())) }
}
}

View file

@ -0,0 +1,83 @@
/*
* AbstractCommand.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.buildCmdSyntax
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendMessage
import org.pircbotx.hooks.events.PrivateMessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
import java.util.concurrent.ConcurrentHashMap
abstract class AbstractCommand {
abstract val name: String
abstract val help: List<String>
abstract val isOpOnly: Boolean
abstract val isPublic: Boolean
abstract val isVisible: Boolean
val properties: MutableMap<String, String> = ConcurrentHashMap()
abstract fun commandResponse(channel: String, args: String, event: GenericMessageEvent)
open fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
if (!isOpOnly || isOpOnly == isChannelOp(channel, event)) {
for (h in help) {
event.sendMessage(
buildCmdSyntax(h, event.bot().nick, event is PrivateMessageEvent || !isPublic),
)
}
return true
}
return false
}
open fun initProperties(vararg keys: String) {
keys.forEach {
properties[it] = ""
}
}
open fun isEnabled(): Boolean {
return true
}
open fun matches(message: String): Boolean {
return false
}
open fun setProperty(key: String, value: String) {
properties[key] = value
}
}

View file

@ -0,0 +1,69 @@
/*
* ChannelFeed.kt
*
* Copyright (c) 2004-2022, 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.commands
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.thauvin.erik.mobibot.FeedReader
import net.thauvin.erik.mobibot.Utils.helpFormat
import org.pircbotx.hooks.types.GenericMessageEvent
class ChannelFeed(channel: String) : AbstractCommand() {
override val name = channel
override val help = listOf("To list the last 5 posts from the channel's weblog feed:", helpFormat("%c $channel"))
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val FEED_PROP = "feed"
}
init {
initProperties(FEED_PROP)
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isEnabled()) {
runBlocking {
launch {
FeedReader(properties[FEED_PROP]!!, event).run()
}
}
}
}
override fun isEnabled(): Boolean {
return !properties[FEED_PROP].isNullOrBlank()
}
}

View file

@ -0,0 +1,64 @@
/*
* Cycle.kt
*
* Copyright (c) 2004-2022, 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.commands
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
class Cycle : AbstractCommand() {
private val wait = 10
override val name = "cycle"
override val help = listOf("To have the bot leave the channel and come back:", helpFormat("%c $name"))
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
with(event.bot()) {
if (isChannelOp(channel, event)) {
runBlocking {
sendIRC().message(channel, "${event.user.nick} asked me to leave. I'll be back!")
userChannelDao.getChannel(channel).send().part()
delay(wait * 1000L)
sendIRC().joinChannel(channel)
}
} else {
helpResponse(channel, args, event)
}
}
}
}

View file

@ -1,7 +1,7 @@
/*
* LookupTest.java
* Die.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -30,39 +30,37 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
package net.thauvin.erik.mobibot.commands
import org.testng.annotations.Test;
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
import java.util.Arrays;
class Die : AbstractCommand() {
override val name = "die"
override val help = emptyList<String>()
override val isOpOnly = true
override val isPublic = false
override val isVisible = false
import static org.assertj.core.api.Assertions.assertThat;
/**
* 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
*/
@SuppressWarnings("PMD.AvoidUsingHardCodedIP")
public class LookupTest {
@Test
public void testLookupImpl() {
AbstractModuleTest.testAbstractModule(new Lookup());
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
with(event.bot()) {
if (isChannelOp(channel, event) && (properties[DIE_PROP].isNullOrBlank() || args == properties[DIE_PROP])) {
sendIRC().message(channel, "${event.user?.nick} has just signed my death sentence.")
stopBotReconnect()
sendIRC().quitServer("The Bot is Out There!")
}
}
}
@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");
companion object {
/**
* Max days property.
*/
const val DIE_PROP = "die"
}
@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();
init {
initProperties(DIE_PROP)
}
}

View file

@ -0,0 +1,148 @@
/*
* Ignore.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.buildCmdSyntax
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendList
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.links.LinksMgr
import org.pircbotx.hooks.types.GenericMessageEvent
class Ignore : AbstractCommand() {
private val me = "me"
init {
initProperties(IGNORE_PROP)
}
override val name = IGNORE_CMD
override val help = listOf(
"To ignore a link posted to the channel:",
helpFormat("https://www.foo.bar %n"),
"To check your ignore status:",
helpFormat("%c $name"),
"To toggle your ignore status:",
helpFormat("%c $name $me")
)
private val helpOp = help.plus(
arrayOf("To add/remove nicks from the ignored list:", helpFormat("%c $name <nick> [<nick> ...]"))
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val IGNORE_CMD = "ignore"
const val IGNORE_PROP = IGNORE_CMD
private val ignored = mutableSetOf<String>()
@JvmStatic
fun isNotIgnored(nick: String): Boolean {
return !ignored.contains(nick.lowercase())
}
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val isMe = args.trim().equals(me, true)
if (isMe || !isChannelOp(channel, event)) {
val nick = event.user.nick.lowercase()
ignoreNick(nick, isMe, event)
} else {
ignoreOp(args, event)
}
}
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
return if (isChannelOp(channel, event)) {
for (h in helpOp) {
event.sendMessage(buildCmdSyntax(h, event.bot().nick, true))
}
true
} else {
super.helpResponse(channel, topic, event)
}
}
private fun ignoreNick(sender: String, isMe: Boolean, event: GenericMessageEvent) {
if (isMe) {
if (ignored.remove(sender)) {
event.sendMessage("You are no longer ignored.")
} else {
ignored.add(sender)
event.sendMessage("You are now ignored.")
}
} else {
if (ignored.contains(sender)) {
event.sendMessage("You are currently ignored.")
} else {
event.sendMessage("You are not currently ignored.")
}
}
}
private fun ignoreOp(args: String, event: GenericMessageEvent) {
if (args.isNotEmpty()) {
val nicks = args.lowercase().split(" ")
for (nick in nicks) {
val ignore = if (me == nick) {
nick.lowercase()
} else {
nick
}
if (!ignored.remove(ignore)) {
ignored.add(ignore)
}
}
}
if (ignored.size > 0) {
event.sendMessage("The following nicks are ignored:")
event.sendList(ignored.sorted(), 8, isIndent = true)
} else {
event.sendMessage("No one is currently ${"ignored".bold()}.")
}
}
override fun setProperty(key: String, value: String) {
super.setProperty(key, value)
if (IGNORE_PROP == key) {
ignored.addAll(value.split(LinksMgr.TAG_MATCH.toRegex()))
}
}
}

View file

@ -0,0 +1,76 @@
/*
* Info.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.ReleaseInfo
import net.thauvin.erik.mobibot.Utils.capitalise
import net.thauvin.erik.mobibot.Utils.green
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendList
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.Utils.uptime
import net.thauvin.erik.mobibot.commands.links.LinksMgr
import net.thauvin.erik.mobibot.commands.tell.Tell
import org.pircbotx.hooks.types.GenericMessageEvent
import java.lang.management.ManagementFactory
class Info(private val tell: Tell) : AbstractCommand() {
private val allVersions = listOf(
"${ReleaseInfo.PROJECT.capitalise()} ${ReleaseInfo.VERSION} (${ReleaseInfo.WEBSITE.green()})",
"Written by ${ReleaseInfo.AUTHOR} (${ReleaseInfo.AUTHOR_URL.green()})"
)
override val name = "info"
override val help = listOf("To view information about the bot:", helpFormat("%c $name"))
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
event.sendList(allVersions, 1)
val info = StringBuilder()
info.append("Uptime: ")
.append(uptime(ManagementFactory.getRuntimeMXBean().uptime))
.append(" [Entries: ")
.append(LinksMgr.entries.links.size)
if (isChannelOp(channel, event)) {
if (tell.isEnabled()) {
info.append(", Messages: ").append(tell.size())
}
if (LinksMgr.twitter.isAutoPost) {
info.append(", Twitter: ").append(LinksMgr.twitter.entriesCount())
}
}
info.append(", Recap: ").append(Recap.recaps.size).append(']')
event.sendMessage(info.toString())
}
}

View file

@ -0,0 +1,52 @@
/*
* Me.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
class Me : AbstractCommand() {
override val name = "me"
override val help = listOf("To have the bot perform an action:", helpFormat("%c $name <action>"))
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
event.bot().sendIRC().action(channel, args)
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Modules.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendList
import org.pircbotx.hooks.types.GenericMessageEvent
class Modules(private val modules: List<String>) : AbstractCommand() {
override val name = "modules"
override val help = listOf("To view a list of enabled modules:", helpFormat("%c $name"))
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
if (modules.isEmpty()) {
event.respondPrivateMessage("There are no enabled modules.")
} else {
event.respondPrivateMessage("The enabled modules are: ")
event.sendList(modules, 7, isIndent = true)
}
} else {
helpResponse(channel, args, event)
}
}
}

View file

@ -1,7 +1,7 @@
/*
* ThreadedModule.java
* Msg.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -30,36 +30,32 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules;
package net.thauvin.erik.mobibot.commands
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
/**
* 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
*/
public abstract class ThreadedModule extends AbstractModule {
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (isEnabled() && args.length() > 0) {
new Thread(() -> run(bot, sender, cmd, args)).start();
} else {
helpResponse(bot, sender, args, isPrivate);
class Msg : AbstractCommand() {
override val name = "msg"
override val help = listOf(
"To have the bot send a private message to someone:",
helpFormat("%c $name <nick> <text>")
)
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
val msg = args.split(" ", limit = 2)
if (args.length > 2) {
event.bot().sendIRC().message(msg[0], msg[1])
event.respondPrivateMessage("A message was sent to ${msg[0]}")
} else {
helpResponse(channel, args, event)
}
}
}
/**
* Runs the thread.
*/
abstract void run(Mobibot bot, String sender, @SuppressWarnings("unused") String cmd, String args);
}

View file

@ -0,0 +1,52 @@
/*
* Nick.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
class Nick : AbstractCommand() {
override val name = "nick"
override val help = listOf("To change the bot's nickname:", helpFormat("%c $name <new_nick>"))
override val isOpOnly = true
override val isPublic = true
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
event.bot().sendIRC().changeNick(args)
}
}
}

View file

@ -0,0 +1,82 @@
/*
* Recap.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.Utils.toUtcDateTime
import org.pircbotx.hooks.types.GenericMessageEvent
import java.time.Clock
import java.time.LocalDateTime
class Recap : AbstractCommand() {
override val name = "recap"
override val help = listOf(
"To list the last 10 public channel messages:",
helpFormat("%c $name")
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val MAX_RECAPS = 10
@JvmField
val recaps = mutableListOf<String>()
/**
* Stores the last 10 public messages and actions.
*/
@JvmStatic
fun storeRecap(sender: String, message: String, isAction: Boolean) {
recaps.add(
LocalDateTime.now(Clock.systemUTC()).toUtcDateTime()
+ " - $sender" + (if (isAction) " " else ": ") + message
)
if (recaps.size > MAX_RECAPS) {
recaps.removeFirst()
}
}
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (recaps.isNotEmpty()) {
for (r in recaps) {
event.sendMessage(r)
}
} else {
event.sendMessage("Sorry, nothing to recap.")
}
}
}

View file

@ -0,0 +1,52 @@
/*
* Say.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import org.pircbotx.hooks.types.GenericMessageEvent
class Say : AbstractCommand() {
override val name = "say"
override val help = listOf("To have the bot say something on the channel:", helpFormat("%c $name <text>"))
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
event.bot().sendIRC().message(channel, args)
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Users.kt
*
* Copyright (c) 2004-2022, 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.commands
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.sendList
import org.pircbotx.hooks.types.GenericMessageEvent
class Users : AbstractCommand() {
override val name = "users"
override val help = listOf("To list the users present on the channel:", helpFormat("%c $name"))
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val nicks = mutableListOf<String>()
val ch = event.bot().userChannelDao.getChannel(channel)
ch.users.forEach {
if (it.channelsOpIn.contains(ch)) {
nicks.add("@${it.nick}")
} else {
nicks.add(it.nick)
}
}
event.sendList(nicks, 8)
}
}

View file

@ -1,7 +1,7 @@
/*
* ErrorMessage.java
* Versions.kt
*
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net)
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -29,41 +29,31 @@
* 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.commands
package net.thauvin.erik.mobibot.msg;
import net.thauvin.erik.mobibot.ReleaseInfo
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendList
import net.thauvin.erik.mobibot.Utils.toIsoLocalDate
import org.pircbotx.hooks.types.GenericMessageEvent
/**
* The <code>ErrorMessage</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-07
* @since 1.0
*/
public class ErrorMessage extends Message {
/**
* Creates a new error message.
*
* @param message The error message.
*/
public ErrorMessage(final String message) {
super();
this.setMessage(message);
this.setError(true);
this.setNotice(true);
}
class Versions : AbstractCommand() {
private val allVersions = listOf(
"Version: ${ReleaseInfo.VERSION} (${ReleaseInfo.BUILDDATE.toIsoLocalDate()})",
"Platform: " + System.getProperty("os.name") + ' ' + System.getProperty("os.version")
+ " (" + System.getProperty("os.arch") + ')',
"Runtime: " + System.getProperty("java.runtime.name") + ' ' + System.getProperty("java.runtime.version")
)
override val name = "versions"
override val help = listOf("To view the versions data (bot, platform, java, etc.):", helpFormat("%c $name"))
override val isOpOnly = true
override val isPublic = false
override val isVisible = true
/**
* Creates a new error message.
*
* @param message The message.
* @param color The message color.
*/
@SuppressWarnings("unused")
public ErrorMessage(final String message, final String color) {
super();
this.setMessage(message);
this.setError(true);
this.setNotice(true);
this.setColor(color);
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
event.sendList(allVersions, 1)
}
}
}

View file

@ -0,0 +1,152 @@
/*
* Comment.kt
*
* Copyright (c) 2004-2022, 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.commands.links
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.entries.EntriesUtils.buildComment
import net.thauvin.erik.mobibot.entries.EntriesUtils.buildLinkLabel
import net.thauvin.erik.mobibot.entries.EntryLink
import org.pircbotx.hooks.types.GenericMessageEvent
class Comment : AbstractCommand() {
override val name = COMMAND
override val help = listOf(
"To add a comment:",
helpFormat("${Constants.LINK_CMD}1:This is a comment"),
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1.1",
"To edit a comment, use its label: ",
helpFormat("${Constants.LINK_CMD}1.1:This is an edited comment"),
"To delete a comment, use its label and a minus sign: ",
helpFormat("${Constants.LINK_CMD}1.1:-")
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val COMMAND = "comment"
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val cmds = args.substring(1).split("[.:]".toRegex(), 3)
val entryIndex = cmds[0].toInt() - 1
if (entryIndex < LinksMgr.entries.links.size && LinksMgr.isUpToDate(event)) {
val entry: EntryLink = LinksMgr.entries.links[entryIndex]
val commentIndex = cmds[1].toInt() - 1
if (commentIndex < entry.comments.size) {
when (val cmd = cmds[2].trim()) {
"" -> showComment(entry, entryIndex, commentIndex, event) // L1.1:
"-" -> deleteComment(channel, entry, entryIndex, commentIndex, event) // L1.1:-
else -> {
if (cmd.startsWith('?')) { // L1.1:?<author>
changeAuthor(channel, cmd, entry, entryIndex, commentIndex, event)
} else { // L1.1:<comment>
setComment(cmd, entry, entryIndex, commentIndex, event)
}
}
}
}
}
}
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
if (super.helpResponse(channel, topic, event)) {
if (isChannelOp(channel, event)) {
event.sendMessage("To change a comment's author:")
event.sendMessage(helpFormat("${Constants.LINK_CMD}1.1:?<nick>"))
}
return true
}
return false
}
override fun matches(message: String): Boolean {
return message.matches("^${Constants.LINK_CMD}[0-9]+\\.[0-9]+:.*".toRegex())
}
private fun changeAuthor(
channel: String,
cmd: String,
entry: EntryLink,
entryIndex: Int,
commentIndex: Int,
event: GenericMessageEvent
) {
if (isChannelOp(channel, event) && cmd.length > 1) {
val comment = entry.getComment(commentIndex)
comment.nick = cmd.substring(1)
event.sendMessage(buildComment(entryIndex, commentIndex, comment))
LinksMgr.entries.save()
} else {
event.sendMessage("Please ask a channel op to change the author of this comment for you.")
}
}
private fun deleteComment(
channel: String,
entry: EntryLink,
entryIndex: Int,
commentIndex: Int,
event: GenericMessageEvent
) {
if (isChannelOp(channel, event) || event.user.nick == entry.getComment(commentIndex).nick) {
entry.deleteComment(commentIndex)
event.sendMessage("Comment ${buildLinkLabel(entryIndex)}.${commentIndex + 1} removed.")
LinksMgr.entries.save()
} else {
event.sendMessage("Please ask a channel op to delete this comment for you.")
}
}
private fun setComment(
cmd: String,
entry: EntryLink,
entryIndex: Int,
commentIndex: Int,
event: GenericMessageEvent
) {
entry.setComment(commentIndex, cmd, event.user.nick)
event.sendMessage(buildComment(entryIndex, commentIndex, entry.getComment(commentIndex)))
LinksMgr.entries.save()
}
private fun showComment(entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent) {
event.sendMessage(buildComment(entryIndex, commentIndex, entry.getComment(commentIndex)))
}
}

View file

@ -0,0 +1,208 @@
/*
* LinksMgr.kt
*
* Copyright (c) 2004-2022, 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.commands.links
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Pinboard
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.Utils.today
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.Ignore.Companion.isNotIgnored
import net.thauvin.erik.mobibot.entries.Entries
import net.thauvin.erik.mobibot.entries.EntriesUtils.buildLink
import net.thauvin.erik.mobibot.entries.EntriesUtils.buildLinkLabel
import net.thauvin.erik.mobibot.entries.EntryLink
import net.thauvin.erik.mobibot.modules.Twitter
import org.jsoup.Jsoup
import org.pircbotx.hooks.types.GenericMessageEvent
import java.io.IOException
class LinksMgr : AbstractCommand() {
private val defaultTags: MutableList<String> = mutableListOf()
private val keywords: MutableList<String> = mutableListOf()
override val name = Constants.LINK_CMD
override val help = emptyList<String>()
override val isOpOnly = false
override val isPublic = false
override val isVisible = false
init {
initProperties(TAGS_PROP, KEYWORDS_PROP)
}
companion object {
const val LINK_MATCH = "^[hH][tT][tT][pP](|[sS])://.*"
const val KEYWORDS_PROP = "tags-keywords"
const val TAGS_PROP = "tags"
const val TAG_MATCH = ", *| +"
/**
* Entries array
*/
@JvmField
val entries = Entries()
/**
* Pinboard handler.
*/
@JvmField
val pinboard = Pinboard()
/**
* Twitter handler.
*/
@JvmField
val twitter = Twitter()
/**
* Let the user know if the entries are too old to be modified.
*/
@JvmStatic
fun isUpToDate(event: GenericMessageEvent): Boolean {
if (entries.lastPubDate != today()) {
event.sendMessage("The links are too old to be updated.")
return false
}
return true
}
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val cmds = args.split(" ".toRegex(), 2)
val sender = event.user.nick
val botNick = event.bot().nick
val login = event.user.login
if (isNotIgnored(sender) && (cmds.size == 1 || !cmds[1].contains(botNick))) {
val link = cmds[0].trim()
if (!isDupEntry(link, event)) {
var title = ""
val tags = ArrayList<String>(defaultTags)
if (cmds.size == 2) {
val data = cmds[1].trim().split("${Tags.COMMAND}:", limit = 2)
title = data[0].trim()
if (data.size > 1) {
tags.addAll(data[1].split(TAG_MATCH.toRegex()))
}
}
if (title.isBlank()) {
title = fetchTitle(link)
}
if (title != Constants.NO_TITLE) {
matchTagKeywords(title, tags)
}
// Links are old, clear them
if (entries.lastPubDate != today()) {
entries.links.clear()
}
val entry = EntryLink(link, title, sender, login, channel, tags)
entries.links.add(entry)
val index = entries.links.lastIndexOf(entry)
event.sendMessage(buildLink(index, entry))
pinboard.addPin(event.bot().serverHostname, entry)
// Queue link for posting to Twitter.
twitter.queueEntry(index)
entries.save()
if (Constants.NO_TITLE == entry.title) {
event.sendMessage("Please specify a title, by typing:")
event.sendMessage(helpFormat("${buildLinkLabel(index)}:|This is the title"))
}
}
}
}
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean = false
override fun matches(message: String): Boolean {
return message.matches(LINK_MATCH.toRegex())
}
internal fun fetchTitle(link: String): String {
try {
val html = Jsoup.connect(link)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0")
.get()
val title = html.title()
if (title.isNotBlank()) {
return title
}
} catch (ignore: IOException) {
// Do nothing
}
return Constants.NO_TITLE
}
private fun isDupEntry(link: String, event: GenericMessageEvent): Boolean {
synchronized(entries) {
return try {
val match = entries.links.single { it.link == link }
event.sendMessage(
"Duplicate".bold() + " >> " + buildLink(entries.links.indexOf(match), match)
)
true
} catch (ignore: NoSuchElementException) {
false
}
}
}
internal fun matchTagKeywords(title: String, tags: MutableList<String>) {
for (match in keywords) {
val m = Regex.escape(match)
if (title.matches("(?i).*\\b$m\\b.*".toRegex())) {
tags.add(match)
}
}
}
override fun setProperty(key: String, value: String) {
super.setProperty(key, value)
if (KEYWORDS_PROP == key) {
keywords.addAll(value.split(TAG_MATCH.toRegex()))
} else if (TAGS_PROP == key) {
defaultTags.addAll(value.split(TAG_MATCH.toRegex()))
}
}
}

View file

@ -0,0 +1,164 @@
/*
* Posting.kt
*
* Copyright (c) 2004-2022, 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.commands.links
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.entries
import net.thauvin.erik.mobibot.entries.EntriesUtils
import net.thauvin.erik.mobibot.entries.EntryLink
import org.pircbotx.hooks.types.GenericMessageEvent
class Posting : AbstractCommand() {
override val name = "posting"
override val help = listOf(
"Post a URL, by saying it on a line on its own:",
helpFormat("<url> [<title>] ${Tags.COMMAND}: <+tag> [...]]"),
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1",
"To add a title, use its label and a pipe:",
helpFormat("${Constants.LINK_CMD}1:|This is the title"),
"To add a comment:",
helpFormat("${Constants.LINK_CMD}1:This is a comment"),
"I will reply with a label, for example: ${Constants.LINK_CMD.bold()}1.1",
"To edit a comment, see: ",
helpFormat("%c ${Constants.HELP_CMD} ${Comment.COMMAND}")
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val cmds = args.substring(1).split(":", limit = 2)
val entryIndex = cmds[0].toInt() - 1
if (entryIndex < entries.links.size) {
val cmd = cmds[1].trim()
if (cmd.isBlank()) {
showEntry(entryIndex, event) // L1:
} else if (LinksMgr.isUpToDate(event)) {
if (cmd == "-") {
removeEntry(channel, entryIndex, event) // L1:-
} else {
when (cmd[0]) {
'|' -> changeTitle(cmd, entryIndex, event) // L1:|<title>
'=' -> changeUrl(channel, cmd, entryIndex, event) // L1:=<url>
'?' -> changeAuthor(channel, cmd, entryIndex, event) // L1:?<author>
else -> addComment(cmd, entryIndex, event) // L1:<comment>
}
}
}
}
}
override fun matches(message: String): Boolean {
return message.matches("${Constants.LINK_CMD}[0-9]+:.*".toRegex())
}
private fun addComment(cmd: String, entryIndex: Int, event: GenericMessageEvent) {
val entry: EntryLink = entries.links[entryIndex]
val commentIndex = entry.addComment(cmd, event.user.nick)
val comment = entry.getComment(commentIndex)
event.sendMessage(EntriesUtils.buildComment(entryIndex, commentIndex, comment))
entries.save()
}
private fun changeTitle(cmd: String, entryIndex: Int, event: GenericMessageEvent) {
if (cmd.length > 1) {
val entry: EntryLink = entries.links[entryIndex]
entry.title = cmd.substring(1).trim()
LinksMgr.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
event.sendMessage(EntriesUtils.buildLink(entryIndex, entry))
entries.save()
}
}
private fun changeUrl(channel: String, cmd: String, entryIndex: Int, event: GenericMessageEvent) {
val entry: EntryLink = entries.links[entryIndex]
if (entry.login == event.user.login || isChannelOp(channel, event)) {
val link = cmd.substring(1)
if (link.matches(LinksMgr.LINK_MATCH.toRegex())) {
val oldLink = entry.link
entry.link = link
LinksMgr.pinboard.updatePin(event.bot().serverHostname, oldLink, entry)
event.sendMessage(EntriesUtils.buildLink(entryIndex, entry))
entries.save()
}
}
}
private fun changeAuthor(channel: String, cmd: String, index: Int, event: GenericMessageEvent) {
if (isChannelOp(channel, event)) {
if (cmd.length > 1) {
val entry: EntryLink = entries.links[index]
entry.nick = cmd.substring(1)
LinksMgr.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
event.sendMessage(EntriesUtils.buildLink(index, entry))
entries.save()
}
} else {
event.sendMessage("Please ask a channel op to change the author of this link for you.")
}
}
private fun removeEntry(channel: String, index: Int, event: GenericMessageEvent) {
val entry: EntryLink = entries.links[index]
if (entry.login == event.user.login || isChannelOp(channel, event)) {
LinksMgr.pinboard.deletePin(entry)
LinksMgr.twitter.removeEntry(index)
entries.links.removeAt(index)
event.sendMessage("Entry ${EntriesUtils.buildLinkLabel(index)} removed.")
entries.save()
} else {
event.sendMessage("Please ask a channel op to remove this entry for you.")
}
}
private fun showEntry(index: Int, event: GenericMessageEvent) {
val entry: EntryLink = entries.links[index]
event.sendMessage(EntriesUtils.buildLink(index, entry))
if (entry.tags.isNotEmpty()) {
event.sendMessage(EntriesUtils.buildTags(index, entry))
}
if (entry.comments.isNotEmpty()) {
val comments = entry.comments
for (i in comments.indices) {
event.sendMessage(EntriesUtils.buildComment(index, i, comments[i]))
}
}
}
}

View file

@ -0,0 +1,88 @@
/*
* Tags.kt
*
* Copyright (c) 2004-2022, 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.commands.links
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.entries.EntriesUtils
import net.thauvin.erik.mobibot.entries.EntryLink
import org.pircbotx.hooks.types.GenericMessageEvent
class Tags : AbstractCommand() {
override val name = COMMAND
override val help = listOf(
"To categorize or tag a URL, use its label and a ${Constants.TAG_CMD}:",
helpFormat("${Constants.LINK_CMD}1${Constants.TAG_CMD}:<+tag|-tag> [...]")
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val COMMAND = "tags"
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
val cmds = args.substring(1).split("${Constants.TAG_CMD}:", limit = 2)
val index = cmds[0].toInt() - 1
if (index < LinksMgr.entries.links.size && LinksMgr.isUpToDate(event)) {
val cmd = cmds[1].trim()
val entry: EntryLink = LinksMgr.entries.links[index]
if (cmd.isNotEmpty()) {
if (entry.login == event.user.login || isChannelOp(channel, event)) {
entry.setTags(cmd)
LinksMgr.pinboard.updatePin(event.bot().serverHostname, entry.link, entry)
event.sendMessage(EntriesUtils.buildTags(index, entry))
LinksMgr.entries.save()
} else {
event.sendMessage("Please ask a channel op to change the tags for you.")
}
} else {
if (entry.tags.isNotEmpty()) {
event.sendMessage(EntriesUtils.buildTags(index, entry))
} else {
event.sendMessage("The entry has no tags. Why don't add some?")
}
}
}
}
override fun matches(message: String): Boolean {
return message.matches("^${Constants.LINK_CMD}[0-9]+${Constants.TAG_CMD}:.*".toRegex())
}
}

View file

@ -0,0 +1,125 @@
/*
* View.kt
*
* Copyright (c) 2004-2022, 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.commands.links
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.buildCmdSyntax
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.lastOrEmpty
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.entries
import net.thauvin.erik.mobibot.entries.EntriesUtils
import net.thauvin.erik.mobibot.entries.EntryLink
import org.pircbotx.hooks.events.PrivateMessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
class View : AbstractCommand() {
override val name = VIEW_CMD
override val help = listOf(
"To list or search the current URL posts:",
helpFormat("%c $name [<start>] [<query>]")
)
override val isOpOnly = false
override val isPublic = true
override val isVisible = true
companion object {
const val MAX_ENTRIES = 6
const val VIEW_CMD = "view"
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (entries.links.isNotEmpty()) {
val p = parseArgs(args)
showPosts(p.first, p.second, event)
} else {
event.sendMessage("There is currently nothing to view. Why don't you post something?")
}
}
internal fun parseArgs(args: String): Pair<Int, String> {
var query = args.lowercase().trim()
var start = 0
if (query.isEmpty() && entries.links.size > MAX_ENTRIES) {
start = entries.links.size - MAX_ENTRIES
}
if (query.matches("^\\d+(| .*)".toRegex())) { // view [<start>] [<query>]
val split = query.split(" ", limit = 2)
try {
start = split[0].toInt() - 1
query = split.lastOrEmpty().trim()
if (start > entries.links.size) {
start = 0
}
} catch (ignore: NumberFormatException) {
// Do nothing
}
}
return Pair(start, query)
}
private fun showPosts(start: Int, query: String, event: GenericMessageEvent) {
var index = start
var entry: EntryLink
var sent = 0
while (index < entries.links.size && sent < MAX_ENTRIES) {
entry = entries.links[index]
if (query.isNotBlank()) {
if (entry.matches(query)) {
event.sendMessage(EntriesUtils.buildLink(index, entry, true))
sent++
}
} else {
event.sendMessage(EntriesUtils.buildLink(index, entry, true))
sent++
}
index++
if (sent == MAX_ENTRIES && index < entries.links.size) {
event.sendMessage("To view more, try: ")
event.sendMessage(
helpFormat(
buildCmdSyntax(
"%c $name ${index + 1} $query",
event.bot().nick,
event is PrivateMessageEvent
)
)
)
}
}
if (sent == 0) {
event.sendMessage("No matches. Please try again.")
}
}
}

View file

@ -0,0 +1,303 @@
/*
* Tell.kt
*
* Copyright (c) 2004-2022, 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.commands.tell
import net.thauvin.erik.mobibot.Utils.bold
import net.thauvin.erik.mobibot.Utils.bot
import net.thauvin.erik.mobibot.Utils.buildCmdSyntax
import net.thauvin.erik.mobibot.Utils.helpFormat
import net.thauvin.erik.mobibot.Utils.isChannelOp
import net.thauvin.erik.mobibot.Utils.plural
import net.thauvin.erik.mobibot.Utils.reverseColor
import net.thauvin.erik.mobibot.Utils.sendMessage
import net.thauvin.erik.mobibot.Utils.toIntOrDefault
import net.thauvin.erik.mobibot.Utils.toUtcDateTime
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.links.View
import org.pircbotx.PircBotX
import org.pircbotx.hooks.events.MessageEvent
import org.pircbotx.hooks.types.GenericMessageEvent
import org.pircbotx.hooks.types.GenericUserEvent
/**
* The `Tell` command.
*/
class Tell(private val serialObject: String) : AbstractCommand() {
// Messages queue
private val messages: MutableList<TellMessage> = mutableListOf()
// Maximum number of days to keep messages
private var maxDays = 7
// Message maximum queue size
private var maxSize = 50
/**
* The tell command.
*/
override val name = "tell"
override val help = listOf(
"To send a message to someone when they join the channel:",
helpFormat("%c $name <nick> <message>"),
"To view queued and sent messages:",
helpFormat("%c $name ${View.VIEW_CMD}"),
"Messages are kept for ${maxDays.bold()}" + " day".plural(maxDays.toLong()) + '.'
)
override val isOpOnly: Boolean = false
override val isPublic: Boolean = isEnabled()
override val isVisible: Boolean = isEnabled()
/**
* Cleans the messages queue.
*/
private fun clean(): Boolean {
return TellMessagesMgr.clean(messages, maxDays.toLong())
}
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
if (isEnabled()) {
if (args.isBlank()) {
helpResponse(channel, args, event)
} else if (args.startsWith(View.VIEW_CMD)) {
if (isChannelOp(channel, event) && "${View.VIEW_CMD} $TELL_ALL_KEYWORD" == args) {
viewAll(event)
} else {
viewMessages(event)
}
} else if (args.startsWith("$TELL_DEL_KEYWORD ")) {
deleteMessage(channel, args, event)
} else {
newMessage(channel, args, event)
}
if (clean()) {
save()
}
}
}
// Delete message.
private fun deleteMessage(channel: String, args: String, event: GenericMessageEvent) {
val split = args.split(" ")
if (split.size == 2) {
val id = split[1]
if (TELL_ALL_KEYWORD.equals(id, ignoreCase = true)) {
if (messages.removeIf { it.sender.equals(event.user.nick, true) && it.isReceived }) {
save()
event.sendMessage("Delivered messages have been deleted.")
} else {
event.sendMessage("No delivered messages were found.")
}
} else {
if (messages.removeIf {
it.id == id &&
(it.sender.equals(event.user.nick, true) || isChannelOp(channel, event))
}) {
save()
event.sendMessage("The message was deleted from the queue.")
} else {
event.sendMessage("The specified message [ID $id] could not be found.")
}
}
} else {
helpResponse(channel, args, event)
}
}
override fun isEnabled(): Boolean {
return maxSize > 0 && maxDays > 0
}
override fun setProperty(key: String, value: String) {
super.setProperty(key, value)
if (MAX_DAYS_PROP == key) {
maxDays = value.toIntOrDefault(maxDays)
} else if (MAX_SIZE_PROP == key) {
maxSize = value.toIntOrDefault(maxSize)
}
}
// New message.
private fun newMessage(channel: String, args: String, event: GenericMessageEvent) {
val split = args.split(" ".toRegex(), 2)
if (split.size == 2 && split[1].isNotBlank() && split[1].contains(" ")) {
if (messages.size < maxSize) {
val message = TellMessage(event.user.nick, split[0], split[1].trim())
messages.add(message)
save()
event.sendMessage("Message [ID ${message.id}] was queued for ${message.recipient.bold()}")
} else {
event.sendMessage("Sorry, the messages queue is currently full.")
}
} else {
helpResponse(channel, args, event)
}
}
/**
* Saves the messages queue.
*/
private fun save() {
TellMessagesMgr.save(serialObject, messages)
}
/**
* Checks and sends messages.
*/
fun send(event: GenericUserEvent) {
val nickname = event.user.nick
if (isEnabled() && nickname != event.getBot<PircBotX>().nick) {
messages.filter { it.isMatch(nickname) }.forEach { message ->
if (message.recipient.equals(nickname, ignoreCase = true) && !message.isReceived) {
if (message.sender == nickname) {
if (event !is MessageEvent) {
event.user.send().message(
"${"You".bold()} wanted me to remind you: ${message.message.reverseColor()}"
)
message.isReceived = true
message.isNotified = true
save()
}
} else {
event.user.send().message(
"${message.sender} wanted me to tell you: ${message.message.reverseColor()}"
)
message.isReceived = true
save()
}
} else if (message.sender.equals(nickname, ignoreCase = true) && message.isReceived
&& !message.isNotified
) {
event.user.send().message(
"Your message ${"[ID ${message.id}]".reverseColor()} was sent to "
+ "${message.recipient.bold()} on ${message.receptionDate}"
)
message.isNotified = true
save()
}
}
}
}
/**
* Returns the messages queue size.
*
* @return The size.
*/
fun size(): Int = messages.size
// View all messages.
private fun viewAll(event: GenericMessageEvent) {
if (messages.isNotEmpty()) {
for (message in messages) {
event.sendMessage(
"${message.sender.bold()}$ARROW${message.recipient.bold()} [ID: ${message.id}, " +
(if (message.isReceived) "DELIVERED]" else "QUEUED]")
)
}
} else {
event.sendMessage("There are no messages in the queue.")
}
}
// View messages.
private fun viewMessages(event: GenericMessageEvent) {
var hasMessage = false
for (message in messages.filter { it.isMatch(event.user.nick) }) {
if (!hasMessage) {
hasMessage = true
event.sendMessage("Here are your messages: ")
}
if (message.isReceived) {
event.sendMessage(
message.sender.bold() + ARROW + message.recipient.bold() +
" [${message.receptionDate.toUtcDateTime()}, ID: ${message.id.bold()}, DELIVERED]"
)
} else {
event.sendMessage(
message.sender.bold() + ARROW + message.recipient.bold() +
" [${message.queued.toUtcDateTime()}, ID: ${message.id.bold()}, QUEUED]"
)
}
event.sendMessage(helpFormat(message.message))
}
if (!hasMessage) {
event.sendMessage("You have no messages in the queue.")
} else {
event.sendMessage("To delete one or all delivered messages:")
event.sendMessage(
helpFormat(
buildCmdSyntax(
"%c $name $TELL_DEL_KEYWORD <id|$TELL_ALL_KEYWORD>",
event.bot().nick,
true
)
)
)
event.sendMessage(help.last())
}
}
companion object {
/**
* Max days property.
*/
const val MAX_DAYS_PROP = "tell-max-days"
/**
* Max size property.
*/
const val MAX_SIZE_PROP = "tell-max-size"
// Arrow
private const val ARROW = " --> "
// All keyword
private const val TELL_ALL_KEYWORD = "all"
//T he delete command.
private const val TELL_DEL_KEYWORD = "del"
}
/**
* Creates a new instance.
*/
init {
initProperties(MAX_DAYS_PROP, MAX_SIZE_PROP)
// Load the message queue
messages.addAll(TellMessagesMgr.load(serialObject))
if (clean()) {
save()
}
}
}

View file

@ -0,0 +1,104 @@
/*
* TellMessage.kt
*
* Copyright (c) 2004-2022, 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.commands.tell
import java.io.Serializable
import java.time.Clock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/**
* The `TellMessage` class.
*/
class TellMessage(
/**
* Returns the message's sender.
*/
val sender: String,
/**
* Returns the message's recipient.
*/
val recipient: String,
/**
* Returns the message text.
*/
val message: String
) : Serializable {
/**
* Returns the queued date/time.
*/
var queued: LocalDateTime = LocalDateTime.now(Clock.systemUTC())
/**
* Returns the message id.
*/
var id: String = queued.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
/**
* Returns {@code true} if a notification was sent.
*/
var isNotified = false
/**
* Returns {@code true} if the message was received.
*/
var isReceived = false
set(value) {
if (value) {
receptionDate = LocalDateTime.now(Clock.systemUTC())
}
field = value
}
/**
* Returns the message creating date.
*/
var receptionDate: LocalDateTime = LocalDateTime.MIN
/**
* Matches the message sender or recipient.
*/
fun isMatch(nick: String?): Boolean {
return sender.equals(nick, ignoreCase = true) || recipient.equals(nick, ignoreCase = true)
}
override fun toString(): String {
return ("TellMessage{id='$id', isNotified=$isNotified, isReceived=$isReceived, message='$message', " +
"queued=$queued, received=$receptionDate, recipient='$recipient', sender='$sender'}")
}
companion object {
private const val serialVersionUID = 2L
}
}

Some files were not shown because too many files have changed in this diff Show more