Removed twitter module

This commit is contained in:
Erik C. Thauvin 2023-05-21 00:01:01 -07:00
parent c2c5f8beae
commit 207c1b7e02
29 changed files with 360 additions and 416 deletions

View file

@ -44,11 +44,6 @@ jobs:
CHATGPT_API_KEY: ${{ secrets.CHATGPT_API_KEY }}
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 }}
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
MASTODON_HANDLE: ${{ secrets.MASTODON_HANDLE }}
MASTODON_INSTANCE: ${{ secrets.MASTODON_INSTANCE }}

View file

@ -3,6 +3,281 @@
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="IF_BRACE_FORCE" value="1" />
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PUBLIC>true</PUBLIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PROTECTED>true</PROTECTED>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<FINAL>true</FINAL>
<PRIVATE>true</PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PUBLIC>true</PUBLIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PROTECTED>true</PROTECTED>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PACKAGE_PRIVATE>true</PACKAGE_PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>true</PRIVATE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<FIELD>true</FIELD>
<PRIVATE>false</PRIVATE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<FIELD>true</FIELD>
</match>
</rule>
</section>
<section>
<rule>
<match>
<INITIALIZER_BLOCK>true</INITIALIZER_BLOCK>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CONSTRUCTOR>true</CONSTRUCTOR>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<METHOD>true</METHOD>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<METHOD>true</METHOD>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<ENUM>true</ENUM>
</match>
</rule>
</section>
<section>
<rule>
<match>
<INTERFACE>true</INTERFACE>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<CLASS>true</CLASS>
<STATIC>true</STATIC>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<CLASS>true</CLASS>
</match>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>

View file

@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

2
.idea/kotlinc.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.10" />
<option name="version" value="1.8.21" />
</component>
</project>

View file

@ -1,13 +1,16 @@
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
plugins {
id 'application'
id 'com.github.ben-manes.versions' version '0.45.0'
id 'com.github.ben-manes.versions' version '0.46.0'
id 'idea'
id 'io.gitlab.arturbosch.detekt' version '1.22.0'
id 'java'
id 'net.thauvin.erik.gradle.semver' version '1.0.4'
id 'org.jetbrains.kotlin.jvm' version '1.8.10'
id 'org.jetbrains.kotlin.kapt' version '1.8.10'
id 'org.jetbrains.kotlinx.kover' version '0.6.1'
id 'org.jetbrains.kotlin.jvm' version '1.8.21'
id 'org.jetbrains.kotlin.kapt' version '1.8.21'
id 'org.jetbrains.kotlinx.kover' version '0.7.0'
id 'org.sonarqube' version '4.0.0.2929'
id 'pmd'
}
@ -29,8 +32,8 @@ def isNonStable = { String version ->
mainClassName = packageName + '.Mobibot'
ext.versions = [
log4j: '2.19.0',
pmd : '6.54.0',
log4j: '2.20.0',
pmd : '6.55.0',
]
repositories {
@ -61,22 +64,21 @@ dependencies {
// Kotlin
implementation platform('org.jetbrains.kotlin:kotlin-bom')
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.5'
// Logging
implementation 'org.slf4j:slf4j-api:2.0.6'
implementation 'org.slf4j:slf4j-api:2.0.7'
implementation "org.apache.logging.log4j:log4j-api:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-core:$versions.log4j"
implementation "org.apache.logging.log4j:log4j-slf4j2-impl:$versions.log4j"
implementation 'com.rometools:rome:1.19.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.rometools:rome:2.1.0'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'net.aksingh:owm-japis:2.5.3.0'
implementation 'net.objecthunter:exp4j:0.4.8'
implementation 'org.json:json:20220924'
implementation 'org.jsoup:jsoup:1.15.4'
implementation 'org.twitter4j:twitter4j-core:4.1.2'
implementation 'org.json:json:20230227'
implementation 'org.jsoup:jsoup:1.16.1'
// Thauvin
implementation 'net.thauvin.erik:cryptoprice:1.0.0'
@ -84,10 +86,10 @@ dependencies {
implementation 'net.thauvin.erik:pinboard-poster:1.0.3'
implementation 'net.thauvin.erik:urlencoder:1.3.0'
testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.25'
testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.26.1'
// testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
// testImplementation "org.mockito:mockito-core:4.0.0"
testImplementation 'org.testng:testng:7.7.1'
testImplementation 'org.testng:testng:7.8.0'
}
test {
@ -114,6 +116,12 @@ java {
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
kapt {
includeCompileClasspath = false
arguments {
@ -121,15 +129,10 @@ kapt {
}
}
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = java.targetCompatibility.toString()
}
}
compileJava {
dependsOn 'incrementBuildMeta'
@ -155,15 +158,14 @@ detekt {
baseline = file("${projectDir}/config/detekt/baseline.xml")
}
tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach {
tasks.withType(Detekt).configureEach {
jvmTarget = java.targetCompatibility.toString()
}
tasks.withType(io.gitlab.arturbosch.detekt.DetektCreateBaselineTask).configureEach {
tasks.withType(DetektCreateBaselineTask).configureEach {
jvmTarget = java.targetCompatibility.toString()
}
jar {
manifest.attributes('Main-Class': mainClassName,
'Class-Path': '. ./lib/' + configurations.runtimeClasspath.collect { it.getName() }.join(' ./lib/'))
@ -204,19 +206,19 @@ tasks.sonar {
dependsOn 'koverReport'
}
task copyToDeploy(type: Copy) {
tasks.register('copyToDeploy', Copy) {
from('properties', jar)
into deployDir
}
task copyToDeployLib(type: Copy) {
tasks.register('copyToDeployLib', Copy) {
from(configurations.runtimeClasspath) {
exclude 'annotations-*.jar'
}
into(deployDir + '/lib')
}
task deploy {
tasks.register('deploy') {
description = "Copies all needed files to the ${deployDir} directory."
group = 'Publishing'
dependsOn(assemble, jar)
@ -228,7 +230,7 @@ task deploy {
mustRunAfter(clean)
}
task release {
tasks.register('release') {
group = 'Publishing'
description = 'Releases new version.'
dependsOn(clean, check, deploy)

View file

@ -10,7 +10,6 @@
<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:ChatGpt.kt$ChatGpt$400</ID>
<ID>MagicNumber:ChatGpt.kt$ChatGpt.Companion$200</ID>
<ID>MagicNumber:ChatGpt.kt$ChatGpt.Companion$429</ID>
@ -33,7 +32,6 @@
<ID>MagicNumber:StockQuote.kt$StockQuote.Companion$10</ID>
<ID>MagicNumber:Tell.kt$Tell$50</ID>
<ID>MagicNumber:Tell.kt$Tell$7</ID>
<ID>MagicNumber:TwitterOAuth.kt$TwitterOAuth$401</ID>
<ID>MagicNumber:Users.kt$Users$8</ID>
<ID>MagicNumber:Utils.kt$Utils$200</ID>
<ID>MagicNumber:Utils.kt$Utils$399</ID>
@ -47,7 +45,6 @@
<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>MaxLineLength:TwitterOAuth.kt$TwitterOAuth$*</ID>
<ID>NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand): Boolean</ID>
<ID>NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule): Boolean</ID>
<ID>NestedBlockDepth:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?, maxTokens: Int): String</ID>
@ -65,13 +62,10 @@
<ID>NestedBlockDepth:Seen.kt$Seen$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:TwitterOAuth.kt$TwitterOAuth$@JvmStatic fun main(args: Array&lt;String&gt;)</ID>
<ID>NestedBlockDepth:Utils.kt$Utils$@JvmStatic fun loadSerialData(file: String, default: Any, logger: Logger, description: String): Any</ID>
<ID>NestedBlockDepth:Utils.kt$Utils$@JvmStatic fun saveSerialData(file: String, data: Any, logger: Logger, description: String)</ID>
<ID>NestedBlockDepth:Weather2.kt$Weather2$override fun commandResponse(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>PrintStackTrace:TwitterOAuth.kt$TwitterOAuth$ioe</ID>
<ID>PrintStackTrace:TwitterOAuth.kt$TwitterOAuth$te</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>
@ -91,5 +85,6 @@
<ID>TooManyFunctions:EntryLink.kt$EntryLink : Serializable</ID>
<ID>TooManyFunctions:Mobibot.kt$Mobibot : ListenerAdapter</ID>
<ID>TooManyFunctions:Tell.kt$Tell : AbstractCommand</ID>
<ID>WildcardImport:FeedReaderTest.kt$import assertk.assertions.*</ID>
</CurrentIssues>
</SmellBaseline>

Binary file not shown.

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

7
gradlew vendored
View file

@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# 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
@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# 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"'
# 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

View file

@ -25,28 +25,13 @@ tell-max-days=5
tell-max-size=50
#disabled-commands=die, ignore
disabled-modules=twitter
disabled-modules=mastodon
#
# API Token for: https://pinboard.in/settings/password
#
#pinboard-api-token=user\:TOKEN
#
# Configure app at: https://developer.twitter.com/
# and then: java -cp mobibot.jar net.thauvin.erik.mobibot.TwitterOAuth <consumerKey> <consumerSecret>
#
#twitter-consumerKey=
#twitter-consumerSecret=
#twitter-token=
#twitter-tokenSecret=
# Twitter handle to receive channel join/leave notifications
#twitter-handle=
# Automatically post links to Mastodon
#twitter-auto-post=true
#
# Create a Mastodon application access token at: https//SERVER_INSTANCE/settings/applications
# Make sure the 'write:statuses' scope is enabled.

View file

@ -59,6 +59,12 @@ object Constants {
*/
const val CLI_CMD = "java -jar ${ReleaseInfo.PROJECT}.jar"
/**
* User-Agent
*/
const val USER_AGENT =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
/**
* The help command.
*/

View file

@ -74,7 +74,7 @@ class FeedReader(private val url: String, val event: GenericMessageEvent) : Runn
fun readFeed(url: String, maxItems: Int = 5): List<Message> {
val messages = mutableListOf<Message>()
val input = SyndFeedInput()
XmlReader(URL(url)).use { reader ->
XmlReader(URL(url).openStream()).use { reader ->
val feed = input.build(reader)
val items = feed.entries
if (items.isEmpty()) {

View file

@ -78,7 +78,6 @@ import net.thauvin.erik.mobibot.modules.Mastodon
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.Twitter
import net.thauvin.erik.mobibot.modules.War
import net.thauvin.erik.mobibot.modules.Weather2
import net.thauvin.erik.mobibot.modules.WolframAlpha
@ -438,7 +437,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
addons.add(View())
// Load social modules
LinksManager.socialManager.add(addons, Twitter(), Mastodon())
LinksManager.socialManager.add(addons, Mastodon())
// Load the modules
addons.add(Calc())

View file

@ -1,118 +0,0 @@
/*
* TwitterOAuth.kt
*
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
*
* 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.AccessToken
import twitter4j.OAuthAuthorization
import twitter4j.TwitterException
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 [Yusuke Yamamoto](https://github.com/Twitter4J/Twitter4J/blob/main/twitter4j-examples/src/main/java/examples/oauth/GetAccessToken.java)
*/
object TwitterOAuth {
/**
* Twitter OAuth Client Registration.
*
* @param args The consumerKey and consumerSecret should be passed as arguments.
*/
@JvmStatic
fun main(args: Array<String>) {
if (args.size == 2) {
try {
val oAuthAuthorization = OAuthAuthorization.getInstance(args[0], args[1])
val requestToken = oAuthAuthorization.oAuthRequestToken
var accessToken: AccessToken? = null
val br = BufferedReader(InputStreamReader(System.`in`))
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()) {
oAuthAuthorization.getOAuthAccessToken(requestToken, pin)
} else {
oAuthAuthorization.getOAuthAccessToken(requestToken)
}
} catch (te: TwitterException) {
if (401 == te.statusCode) {
println("Unable to get the access token.")
} else {
te.printStackTrace()
}
}
}
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) {
te.printStackTrace()
println("Failed to get accessToken: " + te.message)
exitProcess(-1)
} catch (ioe: IOException) {
ioe.printStackTrace()
println("Failed to read the system input.")
exitProcess(-1)
}
} else {
println("Usage: ${TwitterOAuth::class.java.name} <consumerKey> <consumerSecret>")
}
exitProcess(0)
}
}

View file

@ -31,6 +31,7 @@
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.Utils.sendMessage
import org.apache.commons.text.WordUtils
@ -106,6 +107,7 @@ class ChatGpt : AbstractModule() {
.uri(URI.create(API_URL))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer $apiKey")
.header("User-Agent", Constants.USER_AGENT)
.POST(
HttpRequest.BodyPublishers.ofString(
"""{
@ -136,8 +138,10 @@ class ChatGpt : AbstractModule() {
}
} else {
if (response.statusCode() == 429) {
throw ModuleException("$CHATGPT_CMD($query): Rate limit reached",
"Rate limit reached. Please try again later.")
throw ModuleException(
"$CHATGPT_CMD($query): Rate limit reached",
"Rate limit reached. Please try again later."
)
} else {
throw IOException("HTTP Status Code: " + response.statusCode())
}

View file

@ -1,133 +0,0 @@
/*
* Twitter.kt
*
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
*
* 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.helpFormat
import net.thauvin.erik.mobibot.entries.EntryLink
import net.thauvin.erik.mobibot.social.SocialModule
import twitter4j.TwitterException
/**
* The Twitter module.
*/
class Twitter : SocialModule() {
override val name = "Twitter"
override val handle: String?
get() = properties[HANDLE_PROP]
override val isAutoPost: Boolean
get() = isEnabled && properties[AUTO_POST_PROP].toBoolean()
override val isValidProperties: Boolean
get() = !(properties[CONSUMER_KEY_PROP].isNullOrBlank() || properties[CONSUMER_SECRET_PROP].isNullOrBlank()
|| properties[TOKEN_PROP].isNullOrBlank() || properties[TOKEN_SECRET_PROP].isNullOrBlank())
/**
* Formats the entry for posting.
*/
override fun formatEntry(entry: EntryLink): String {
return "${entry.title} ${entry.link} via ${entry.nick} on ${entry.channel}"
}
/**
* Posts on Twitter.
*/
@Throws(ModuleException::class)
override fun post(message: String, isDm: Boolean): String {
return tweet(
consumerKey = properties[CONSUMER_KEY_PROP],
consumerSecret = properties[CONSUMER_SECRET_PROP],
token = properties[TOKEN_PROP],
tokenSecret = properties[TOKEN_SECRET_PROP],
handle = handle,
message = message,
isDm = isDm
)
}
companion object {
// Property keys
const val AUTO_POST_PROP = "twitter-auto-post"
const val CONSUMER_KEY_PROP = "twitter-consumerKey"
const val CONSUMER_SECRET_PROP = "twitter-consumerSecret"
const val HANDLE_PROP = "twitter-handle"
const val TOKEN_PROP = "twitter-token"
const val TOKEN_SECRET_PROP = "twitter-tokenSecret"
// Twitter commands
private const val TWITTER_CMD = "twitter"
private const val TWEET_CMD = "tweet"
/**
* Post on Twitter.
*/
@JvmStatic
@Throws(ModuleException::class)
fun tweet(
consumerKey: String?,
consumerSecret: String?,
token: String?,
tokenSecret: String?,
handle: String?,
message: String,
isDm: Boolean
): String {
return try {
val twitter = twitter4j.Twitter.newBuilder()
.prettyDebugEnabled(true)
.oAuthConsumer(consumerKey, consumerSecret)
.oAuthAccessToken(token, tokenSecret)
.build()
if (!isDm) {
val status = twitter.v1().tweets().updateStatus(message)
"Your message was posted to https://twitter.com/${
twitter.v1().users().accountSettings.screenName
}/statuses/${status.id}"
} else {
val dm = twitter.v1().directMessages().sendDirectMessage(handle, message)
dm.text
}
} catch (e: TwitterException) {
throw ModuleException("tweet($message)", "An error has occurred: ${e.message}", e)
}
}
}
init {
commands.add(TWITTER_CMD)
commands.add(TWEET_CMD)
help.add("To $TWEET_CMD on $name:")
help.add(helpFormat("%c $TWEET_CMD <message>"))
properties[AUTO_POST_PROP] = "false"
initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP)
}
}

View file

@ -45,7 +45,6 @@ import net.thauvin.erik.mobibot.modules.Dice
import net.thauvin.erik.mobibot.modules.Joke
import net.thauvin.erik.mobibot.modules.Lookup
import net.thauvin.erik.mobibot.modules.RockPaperScissors
import net.thauvin.erik.mobibot.modules.Twitter
import net.thauvin.erik.mobibot.modules.War
import org.testng.annotations.Test
import java.util.Properties
@ -62,7 +61,6 @@ class AddonsTest {
// Modules
addons.add(Joke())
addons.add(RockPaperScissors())
addons.add(Twitter()) // no properties, disabled.
addons.add(War())
addons.add(Dice())
addons.add(Lookup())

View file

@ -31,14 +31,9 @@
package net.thauvin.erik.mobibot
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.index
import assertk.assertions.isEqualTo
import assertk.assertions.isFailure
import assertk.assertions.isInstanceOf
import assertk.assertions.prop
import assertk.assertions.size
import assertk.assertions.*
import com.rometools.rome.io.FeedException
import net.thauvin.erik.mobibot.FeedReader.Companion.readFeed
import net.thauvin.erik.mobibot.msg.Message
@ -66,13 +61,13 @@ class FeedReaderTest {
assertThat(messages, "messages").size().isEqualTo(84)
assertThat(messages.last(), "messages.last").prop(Message::msg).contains("techdigest.tv")
assertThat { readFeed("blah") }.isFailure().isInstanceOf(MalformedURLException::class.java)
assertFailure { readFeed("blah") }.isInstanceOf(MalformedURLException::class.java)
assertThat { readFeed("https://www.example.com") }.isFailure().isInstanceOf(FeedException::class.java)
assertFailure { readFeed("https://www.example.com") }.isInstanceOf(FeedException::class.java)
assertThat { readFeed("https://www.thauvin.net/foo") }.isFailure().isInstanceOf(IOException::class.java)
assertFailure { readFeed("https://www.thauvin.net/foo") }.isInstanceOf(IOException::class.java)
assertThat { readFeed("https://www.examplesfoo.com/") }.isFailure()
assertFailure { readFeed("https://www.examplesfoo.com/") }
.isInstanceOf(UnknownHostException::class.java)
}
}

View file

@ -30,6 +30,7 @@
*/
package net.thauvin.erik.mobibot.modules
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isFailure
@ -48,6 +49,6 @@ class CalcTest {
assertThat(calculate("1 + 1"), "calculate(1+1)").isEqualTo("1+1 = ${2.bold()}")
assertThat(calculate("1 -3"), "calculate(1-3)").isEqualTo("1-3 = ${(-2).bold()}")
assertThat(calculate("pi+π+e+φ"), "calculate(pi+π+e+φ)").isEqualTo("pi+π+e+φ = ${"10.62".bold()}")
assertThat { calculate("one + one") }.isFailure().isInstanceOf(UnknownFunctionOrVariableException::class.java)
assertFailure { calculate("one + one") }.isInstanceOf(UnknownFunctionOrVariableException::class.java)
}
}

View file

@ -30,6 +30,7 @@
*/
package net.thauvin.erik.mobibot.modules
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.hasNoCause
@ -41,8 +42,7 @@ import org.testng.annotations.Test
class ChatGptTest : LocalProperties() {
@Test(groups = ["modules"])
fun testApiKey() {
assertThat { ChatGpt.chat("1 gallon to liter", "", 0) }
.isFailure()
assertFailure { ChatGpt.chat("1 gallon to liter", "", 0) }
.isInstanceOf(ModuleException::class.java)
.hasNoCause()
}
@ -57,8 +57,7 @@ class ChatGptTest : LocalProperties() {
ChatGpt.chat("how do I encode a URL in java?", apiKey, 60)
).contains("URLEncoder")
assertThat { ChatGpt.chat("1 liter to gallon", apiKey, 0) }
.isFailure()
assertFailure { ChatGpt.chat("1 liter to gallon", apiKey, 0) }
.isInstanceOf(ModuleException::class.java)
}
}

View file

@ -31,6 +31,7 @@
package net.thauvin.erik.mobibot.modules
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.hasMessage
@ -59,14 +60,13 @@ class GoogleSearchTest : LocalProperties() {
"searchGoogle(empty)"
).isInstanceOf(ErrorMessage::class.java)
assertThat { searchGoogle("test", "", "apiKey") }.isFailure()
assertFailure { searchGoogle("test", "", "apiKey") }
.isInstanceOf(ModuleException::class.java).hasNoCause()
assertThat { searchGoogle("test", "apiKey", "") }.isFailure()
assertFailure { searchGoogle("test", "apiKey", "") }
.isInstanceOf(ModuleException::class.java).hasNoCause()
assertThat { searchGoogle("test", "apiKey", "cssKey") }
.isFailure()
assertFailure { searchGoogle("test", "apiKey", "cssKey") }
.isInstanceOf(ModuleException::class.java)
.hasMessage("API key not valid. Please pass a valid API key.")
}

View file

@ -32,7 +32,6 @@ package net.thauvin.erik.mobibot.modules
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isSuccess
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.Mastodon.Companion.toot
import org.testng.annotations.Test
@ -42,7 +41,7 @@ class MastodonTest : LocalProperties() {
@Throws(ModuleException::class)
fun testToot() {
val msg = "Testing Mastodon API from ${getHostName()}"
assertThat {
assertThat(
toot(
getProperty(Mastodon.ACCESS_TOKEN_PROP),
getProperty(Mastodon.INSTANCE_PROP),
@ -50,6 +49,6 @@ class MastodonTest : LocalProperties() {
msg,
true
)
}.isSuccess().contains(msg)
).contains(msg)
}
}

View file

@ -31,6 +31,7 @@
package net.thauvin.erik.mobibot.modules
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.hasNoCause
import assertk.assertions.index
@ -78,7 +79,7 @@ class StockQuoteTest : LocalProperties() {
isInstanceOf(ErrorMessage::class.java)
prop(Message::msg).isEqualTo(StockQuote.INVALID_SYMBOL)
}
assertThat { getQuote("test", "") }.isFailure().isInstanceOf(ModuleException::class.java).hasNoCause()
assertFailure { getQuote("test", "") }.isInstanceOf(ModuleException::class.java).hasNoCause()
} catch (e: ModuleException) {
// Avoid displaying api keys in CI logs
if ("true" == System.getenv("CI")) {

View file

@ -1,60 +0,0 @@
/*
* TwitterTest.kt
*
* Copyright 2004-2023 Erik C. Thauvin (erik@thauvin.net)
*
* 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 assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isSuccess
import net.thauvin.erik.mobibot.LocalProperties
import net.thauvin.erik.mobibot.modules.Twitter.Companion.tweet
import org.testng.annotations.Test
/**
* The `TwitterTest` class.
*/
class TwitterTest : LocalProperties() {
@Test(groups = ["modules", "twitter"])
@Throws(ModuleException::class)
fun testTweet() {
val msg = "Testing Twitter API from ${getHostName()}"
assertThat {
tweet(
getProperty(Twitter.CONSUMER_KEY_PROP),
getProperty(Twitter.CONSUMER_SECRET_PROP),
getProperty(Twitter.TOKEN_PROP),
getProperty(Twitter.TOKEN_SECRET_PROP),
getProperty(Twitter.HANDLE_PROP),
msg,
true
)
}.isSuccess().isEqualTo(msg)
}
}

View file

@ -31,6 +31,7 @@
package net.thauvin.erik.mobibot.modules
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.endsWith
@ -116,8 +117,8 @@ class Weather2Test : LocalProperties() {
}
query = "test"
assertThat { getWeather(query, "") }.isFailure().isInstanceOf(ModuleException::class.java).hasNoCause()
assertThat { getWeather(query, null) }.isFailure().isInstanceOf(ModuleException::class.java).hasNoCause()
assertFailure { getWeather(query, "") }.isInstanceOf(ModuleException::class.java).hasNoCause()
assertFailure { getWeather(query, null) }.isInstanceOf(ModuleException::class.java).hasNoCause()
messages = getWeather("", "apikey")
assertThat(messages, "getWeather(empty)").index(0).prop(Message::isError).isTrue()

View file

@ -31,10 +31,10 @@
package net.thauvin.erik.mobibot.modules
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.hasMessage
import assertk.assertions.isFailure
import assertk.assertions.isInstanceOf
import net.thauvin.erik.mobibot.ExceptionSanitizer.sanitize
import net.thauvin.erik.mobibot.LocalProperties
@ -44,13 +44,11 @@ import org.testng.annotations.Test
class WolframAlphaTest : LocalProperties() {
@Test(groups = ["modules"])
fun testAppId() {
assertThat { queryWolfram("1 gallon to liter", appId = "DEMO") }
.isFailure()
assertFailure { queryWolfram("1 gallon to liter", appId = "DEMO") }
.isInstanceOf(ModuleException::class.java)
.hasMessage("Error 1: Invalid appid")
assertThat { queryWolfram("1 gallon to liter", appId = "") }
.isFailure()
assertFailure { queryWolfram("1 gallon to liter", appId = "") }
.isInstanceOf(ModuleException::class.java)
}

View file

@ -30,6 +30,7 @@
*/
package net.thauvin.erik.mobibot.modules
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.endsWith
import assertk.assertions.isSuccess
@ -65,7 +66,7 @@ class WordTimeTest {
@Test(groups = ["modules"])
fun testZones() {
COUNTRIES_MAP.filter { it.value != BEATS_KEYWORD }.forEach {
assertThat { ZoneId.of(it.value) }.isSuccess()
assertThat(ZoneId.of(it.value))
}
}
}

View file

@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle
#Mon Jan 30 22:08:48 PST 2023
version.buildmeta=986
#Sat May 20 23:54:50 PDT 2023
version.buildmeta=1077
version.major=0
version.minor=8
version.patch=0
version.prerelease=rc
version.project=mobibot
version.semver=0.8.0-rc+986
version.semver=0.8.0-rc+1077

View file

@ -46,7 +46,6 @@
<li><a href="https://github.com/ethauvin/pinboard-poster">Pinboard Poster</a></li>
<li><a href="https://github.com/pircbotx/pircbotx">PircBotX</a></li>
<li><a href="https://rometools.github.io/rome/">Rome</a></li>
<li><a href="http://twitter4j.org/en/index.html">Twitter4J</a></li>
<li><a href="https://github.com/ethauvin/urlencoder">UrlEncoder</a></li>
</ul>
<p>mobibot was written by