From 6fd59e9487c6a999020c0537d2a1955c78fd999c Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Mon, 5 Dec 2022 23:50:16 -0800 Subject: [PATCH] Implemented a SocialManager with support for posting to both Twitter and Mastodon --- .github/workflows/gradle.yml | 4 + config/detekt/baseline.xml | 21 +-- properties/mobibot.properties | 19 ++- .../kotlin/net/thauvin/erik/mobibot/Addons.kt | 16 +- .../net/thauvin/erik/mobibot/FeedReader.kt | 4 +- .../net/thauvin/erik/mobibot/Mobibot.kt | 26 +-- .../kotlin/net/thauvin/erik/mobibot/Utils.kt | 16 +- .../erik/mobibot/commands/AbstractCommand.kt | 2 +- .../thauvin/erik/mobibot/commands/Cycle.kt | 2 +- .../net/thauvin/erik/mobibot/commands/Die.kt | 2 +- .../thauvin/erik/mobibot/commands/Ignore.kt | 8 +- .../net/thauvin/erik/mobibot/commands/Info.kt | 10 +- .../net/thauvin/erik/mobibot/commands/Me.kt | 2 +- .../thauvin/erik/mobibot/commands/Modules.kt | 2 +- .../net/thauvin/erik/mobibot/commands/Msg.kt | 2 +- .../net/thauvin/erik/mobibot/commands/Nick.kt | 2 +- .../net/thauvin/erik/mobibot/commands/Say.kt | 2 +- .../thauvin/erik/mobibot/commands/Versions.kt | 2 +- .../erik/mobibot/commands/links/Comment.kt | 16 +- .../links/{LinksMgr.kt => LinksManager.kt} | 14 +- .../erik/mobibot/commands/links/Posting.kt | 22 +-- .../erik/mobibot/commands/links/Tags.kt | 10 +- .../erik/mobibot/commands/links/View.kt | 2 +- .../erik/mobibot/commands/tell/Tell.kt | 10 +- .../{TellMessagesMgr.kt => TellManager.kt} | 6 +- .../thauvin/erik/mobibot/entries/Entries.kt | 4 +- .../thauvin/erik/mobibot/entries/EntryLink.kt | 4 +- .../entries/{FeedsMgr.kt => FeedsManager.kt} | 6 +- .../thauvin/erik/mobibot/modules/Mastodon.kt | 150 ++++++++++++++++++ .../thauvin/erik/mobibot/modules/Twitter.kt | 150 +++--------------- .../erik/mobibot/social/SocialManager.kt | 117 ++++++++++++++ .../erik/mobibot/social/SocialModule.kt | 107 +++++++++++++ .../SocialTimer.kt} | 9 +- .../thauvin/erik/mobibot/LocalProperties.kt | 11 ++ .../{LinksMgrTest.kt => LinksManagerTest.kt} | 21 +-- .../erik/mobibot/commands/links/ViewTest.kt | 6 +- .../commands/tell/TellMessagesMgrTest.kt | 8 +- .../erik/mobibot/entries/FeedMgrTest.kt | 6 +- .../erik/mobibot/modules/MastodonTest.kt | 56 +++++++ .../erik/mobibot/modules/TwitterTest.kt | 20 +-- version.properties | 6 +- website/index.html | 8 +- 42 files changed, 642 insertions(+), 269 deletions(-) rename src/main/kotlin/net/thauvin/erik/mobibot/commands/links/{LinksMgr.kt => LinksManager.kt} (96%) rename src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/{TellMessagesMgr.kt => TellManager.kt} (95%) rename src/main/kotlin/net/thauvin/erik/mobibot/entries/{FeedsMgr.kt => FeedsManager.kt} (99%) create mode 100644 src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt create mode 100644 src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt create mode 100644 src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt rename src/main/kotlin/net/thauvin/erik/mobibot/{TwitterTimer.kt => social/SocialTimer.kt} (88%) rename src/test/kotlin/net/thauvin/erik/mobibot/commands/links/{LinksMgrTest.kt => LinksManagerTest.kt} (75%) create mode 100644 src/test/kotlin/net/thauvin/erik/mobibot/modules/MastodonTest.kt diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e3ef06d..4a41aff 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -57,6 +57,10 @@ jobs: 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 }} + run: ./gradlew build check --stacktrace - name: SonarCloud diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 370754e..d0fbcc9 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -2,9 +2,9 @@ - CyclomaticComplexMethod:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) + CyclomaticComplexMethod:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) CyclomaticComplexMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message> - LongMethod:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) + LongMethod:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) LongMethod:Mobibot.kt$Mobibot.Companion$@JvmStatic @Throws(Exception::class) fun main(args: Array<String>) LongMethod:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message> LongMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message> @@ -23,13 +23,14 @@ MagicNumber:Info.kt$Info.Companion$30 MagicNumber:Info.kt$Info.Companion$365 MagicNumber:Info.kt$Info.Companion$7 + MagicNumber:Mastodon.kt$Mastodon.Companion$200 MagicNumber:Mobibot.kt$Mobibot$8 MagicNumber:Modules.kt$Modules$7 + MagicNumber:SocialManager.kt$SocialManager$1000L + MagicNumber:SocialManager.kt$SocialManager$60L MagicNumber:StockQuote.kt$StockQuote.Companion$10 MagicNumber:Tell.kt$Tell$50 MagicNumber:Tell.kt$Tell$7 - MagicNumber:Twitter.kt$Twitter$1000L - MagicNumber:Twitter.kt$Twitter$60L MagicNumber:TwitterOAuth.kt$TwitterOAuth$401 MagicNumber:Users.kt$Users$8 MagicNumber:Utils.kt$Utils$200 @@ -45,18 +46,19 @@ MagicNumber:WorldTime.kt$WorldTime.Companion$60 MagicNumber:WorldTime.kt$WorldTime.Companion$86.4 MaxLineLength:TwitterOAuth.kt$TwitterOAuth$* - NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand) - NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule) + NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand): Boolean + NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule): Boolean NestedBlockDepth:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): String NestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(query: String): Message NestedBlockDepth:EntryLink.kt$EntryLink$private fun setTags(tags: List<String?>) - NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String - NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) + NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String + NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml) NestedBlockDepth:GoogleSearch.kt$GoogleSearch$override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) NestedBlockDepth:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message> - NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) + NestedBlockDepth:LinksManager.kt$LinksManager$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent) + NestedBlockDepth:Mastodon.kt$Mastodon.Companion$@JvmStatic @Throws(ModuleException::class) fun toot(apiKey: String?, instance: String?, handle: String?, message: String, isDm: Boolean): String NestedBlockDepth:Posting.kt$Posting$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) NestedBlockDepth:Seen.kt$Seen$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) NestedBlockDepth:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message> @@ -77,6 +79,7 @@ ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): String ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message> ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List<Message> + ThrowsCount:Mastodon.kt$Mastodon.Companion$@JvmStatic @Throws(ModuleException::class) fun toot(apiKey: String?, instance: String?, handle: String?, message: String, isDm: Boolean): String ThrowsCount:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message> ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message> diff --git a/properties/mobibot.properties b/properties/mobibot.properties index 981fced..5551173 100644 --- a/properties/mobibot.properties +++ b/properties/mobibot.properties @@ -41,12 +41,25 @@ tell-max-size=50 #twitter-token= #twitter-tokenSecret= -# Twitter handle to receive channel join notifications +# Twitter handle to receive channel join/leave notifications #twitter-handle= -# Automatically post links to twitter +# 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. +# +#mastodon-access-token= +#mastodon-instance=mastodon.social + +# Mastodon handle to receive channel join/leave notifications +#mastodon-handle=@mobitopia + +# Automatically post links to Mastodon +#mastodon-auto-post=true + # # Create custom search engine at: https://cse.google.com/ # and get API key from: https://console.developers.google.com/ @@ -65,7 +78,7 @@ tell-max-size=50 #alphavantage-api-key= # -# Get Wolfram Alpa AppID from: https://developer.wolframalpha.com/portal/ +# Get Wolfram Alpha AppID from: https://developer.wolframalpha.com/portal/ # #wolfram-appid= #wolfram-units=imperial diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/Addons.kt b/src/main/kotlin/net/thauvin/erik/mobibot/Addons.kt index 5a13f50..db5fb25 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/Addons.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/Addons.kt @@ -33,7 +33,7 @@ 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.commands.links.LinksManager import net.thauvin.erik.mobibot.modules.AbstractModule import org.pircbotx.hooks.events.PrivateMessageEvent import org.pircbotx.hooks.types.GenericMessageEvent @@ -46,8 +46,8 @@ import java.util.Properties */ class Addons(private val props: Properties) { private val logger: Logger = LoggerFactory.getLogger(Addons::class.java) - private val disabledModules = props.getProperty("disabled-modules", "").split(LinksMgr.TAG_MATCH.toRegex()) - private val disableCommands = props.getProperty("disabled-commands", "").split(LinksMgr.TAG_MATCH.toRegex()) + private val disabledModules = props.getProperty("disabled-modules", "").split(LinksManager.TAG_MATCH.toRegex()) + private val disableCommands = props.getProperty("disabled-commands", "").split(LinksManager.TAG_MATCH.toRegex()) val commands: MutableList = mutableListOf() val modules: MutableList = mutableListOf() @@ -56,7 +56,8 @@ class Addons(private val props: Properties) { /** * Add a module with properties. */ - fun add(module: AbstractModule) { + fun add(module: AbstractModule): Boolean { + var enabled = false with(module) { if (disabledModules.notContains(name, true)) { if (hasProperties()) { @@ -69,6 +70,7 @@ class Addons(private val props: Properties) { modules.add(this) names.modules.add(name) names.commands.addAll(commands) + enabled = true } else { if (logger.isDebugEnabled) { logger.debug("Module $name is disabled.") @@ -76,12 +78,14 @@ class Addons(private val props: Properties) { } } } + return enabled } /** * Add a command with properties. */ - fun add(command: AbstractCommand) { + fun add(command: AbstractCommand): Boolean { + var enabled = false with(command) { if (disableCommands.notContains(name, true)) { if (properties.isNotEmpty()) { @@ -98,6 +102,7 @@ class Addons(private val props: Properties) { names.commands.add(name) } } + enabled = true } else { if (logger.isDebugEnabled) { logger.debug("Command $name is disabled.") @@ -105,6 +110,7 @@ class Addons(private val props: Properties) { } } } + return enabled } /** diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/FeedReader.kt b/src/main/kotlin/net/thauvin/erik/mobibot/FeedReader.kt index 8fc4f2a..eb00137 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/FeedReader.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/FeedReader.kt @@ -37,7 +37,7 @@ 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.entries.FeedsManager import net.thauvin.erik.mobibot.msg.Message import net.thauvin.erik.mobibot.msg.NoticeMessage import org.pircbotx.hooks.types.GenericMessageEvent @@ -50,7 +50,7 @@ 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) + private val logger: Logger = LoggerFactory.getLogger(FeedsManager::class.java) /** * Fetches the Feed's items. diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt index 94c7a8c..bae8403 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/Mobibot.kt @@ -61,7 +61,7 @@ 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.LinksManager import net.thauvin.erik.mobibot.commands.links.Posting import net.thauvin.erik.mobibot.commands.links.Tags import net.thauvin.erik.mobibot.commands.links.View @@ -75,9 +75,11 @@ 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.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 @@ -146,7 +148,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro ) event.sendMessage("The commands are:") event.sendList(addons.names.commands, 8, isBold = true, isIndent = true) - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.sendMessage("The op commands are:") event.sendList(addons.names.ops, 8, isBold = true, isIndent = true) } @@ -174,11 +176,11 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro override fun onDisconnect(event: DisconnectEvent?) { event?.let { with(event.getBot()) { - LinksMgr.twitter.notification("$nick disconnected from irc://$serverHostname") + LinksManager.socialManager.notification("$nick disconnected from irc://$serverHostname") seen.add(userChannelDao.getChannel(channel).users) } } - LinksMgr.twitter.shutdown() + LinksManager.socialManager.shutdown() } override fun onPrivateMessage(event: PrivateMessageEvent?) { @@ -199,7 +201,9 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro event?.user?.let { user -> with(event.getBot()) { if (user.nick == nick) { - LinksMgr.twitter.notification("$nick has joined ${event.channel.name} on irc://$serverHostname") + LinksManager.socialManager.notification( + "$nick has joined ${event.channel.name} on irc://$serverHostname" + ) seen.add(userChannelDao.getChannel(channel).users) } else { tell.send(event) @@ -247,7 +251,9 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro event?.user?.let { user -> with(event.getBot()) { if (user.nick == nick) { - LinksMgr.twitter.notification("$nick has left ${event.channel.name} on irc://$serverHostname") + LinksManager.socialManager.notification( + "$nick has left ${event.channel.name} on irc://$serverHostname" + ) seen.add(userChannelDao.getChannel(channel).users) } else { seen.add(user.nick) @@ -388,7 +394,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro }.buildConfiguration() // Load the current entries - with(LinksMgr) { + with(LinksManager) { entries.channel = channel entries.ircServer = ircServer entries.logsDir = logsDirPath @@ -407,7 +413,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro addons.add(Cycle()) addons.add(Die()) addons.add(Ignore()) - addons.add(LinksMgr()) + addons.add(LinksManager()) addons.add(Me()) addons.add(Modules(addons.names.modules)) addons.add(Msg()) @@ -426,11 +432,13 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro tell = Tell("${logsDirPath}${nickname}.ser") addons.add(tell) - addons.add(LinksMgr.twitter) addons.add(Users()) addons.add(Versions()) addons.add(View()) + // Load social modules + LinksManager.socialManager.add(addons, Twitter(), Mastodon()) + // Load the modules addons.add(Calc()) addons.add(ChatGpt()) diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/Utils.kt b/src/main/kotlin/net/thauvin/erik/mobibot/Utils.kt index 1ff6300..113a0ab 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/Utils.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/Utils.kt @@ -65,6 +65,18 @@ import kotlin.io.path.fileSize object Utils { private val searchFlags = arrayOf("%c", "%n") + /** + * Prepends a prefix if not present. + */ + @JvmStatic + fun String.prefixIfMissing(prefix: Char): String { + return if (first() != prefix) { + "$prefix${this}" + } else { + this + } + } + /** * Appends a suffix to the end of the String if not present. */ @@ -183,8 +195,8 @@ object Utils { * 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) + fun GenericMessageEvent.isChannelOp(channel: String): Boolean { + return this.bot().userChannelDao.getChannel(channel).isOp(this.user) } /** diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/AbstractCommand.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/AbstractCommand.kt index f773cdd..c404b89 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/AbstractCommand.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/AbstractCommand.kt @@ -51,7 +51,7 @@ abstract class AbstractCommand { 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)) { + if (!isOpOnly || isOpOnly == event.isChannelOp(channel)) { for (h in help) { event.sendMessage(helpCmdSyntax(h, event.bot().nick, event is PrivateMessageEvent || !isPublic)) } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Cycle.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Cycle.kt index 8c6e777..20a49d2 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Cycle.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Cycle.kt @@ -49,7 +49,7 @@ class Cycle : AbstractCommand() { override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { with(event.bot()) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { runBlocking { sendIRC().message(channel, "${event.user.nick} asked me to leave. I'll be back!") userChannelDao.getChannel(channel).send().part() diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Die.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Die.kt index 9ccbcf3..97ca12e 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Die.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Die.kt @@ -45,7 +45,7 @@ class Die : AbstractCommand() { override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { with(event.bot()) { - if (isChannelOp(channel, event) && (properties[DIE_PROP].isNullOrBlank() || args == properties[DIE_PROP])) { + if (event.isChannelOp(channel) && (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!") diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Ignore.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Ignore.kt index df56e34..7306cea 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Ignore.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Ignore.kt @@ -39,7 +39,7 @@ 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 net.thauvin.erik.mobibot.commands.links.LinksManager import org.pircbotx.hooks.types.GenericMessageEvent class Ignore : AbstractCommand() { @@ -79,7 +79,7 @@ class Ignore : AbstractCommand() { override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { val isMe = args.trim().equals(me, true) - if (isMe || !isChannelOp(channel, event)) { + if (isMe || !event.isChannelOp(channel)) { val nick = event.user.nick.lowercase() ignoreNick(nick, isMe, event) } else { @@ -88,7 +88,7 @@ class Ignore : AbstractCommand() { } override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean { - return if (isChannelOp(channel, event)) { + return if (event.isChannelOp(channel)) { for (h in helpOp) { event.sendMessage(helpCmdSyntax(h, event.bot().nick, true)) } @@ -141,7 +141,7 @@ class Ignore : AbstractCommand() { override fun setProperty(key: String, value: String) { super.setProperty(key, value) if (IGNORE_PROP == key) { - ignored.addAll(value.split(LinksMgr.TAG_MATCH.toRegex())) + ignored.addAll(value.split(LinksManager.TAG_MATCH.toRegex())) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Info.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Info.kt index b27fc43..9f7e855 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Info.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Info.kt @@ -39,7 +39,7 @@ import net.thauvin.erik.mobibot.Utils.isChannelOp import net.thauvin.erik.mobibot.Utils.plural import net.thauvin.erik.mobibot.Utils.sendList import net.thauvin.erik.mobibot.Utils.sendMessage -import net.thauvin.erik.mobibot.commands.links.LinksMgr +import net.thauvin.erik.mobibot.commands.links.LinksManager import net.thauvin.erik.mobibot.commands.seen.Seen import net.thauvin.erik.mobibot.commands.tell.Tell import org.pircbotx.hooks.types.GenericMessageEvent @@ -107,16 +107,16 @@ class Info(private val tell: Tell, private val seen: Seen) : AbstractCommand() { info.append("Uptime: ") .append(ManagementFactory.getRuntimeMXBean().uptime.toUptime()) .append(" [Entries: ") - .append(LinksMgr.entries.links.size) + .append(LinksManager.entries.links.size) if (seen.isEnabled()) { info.append(", Seen: ").append(seen.count()) } - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { if (tell.isEnabled()) { info.append(", Messages: ").append(tell.size()) } - if (LinksMgr.twitter.isAutoPost) { - info.append(", Twitter: ").append(LinksMgr.twitter.entriesCount()) + if (LinksManager.socialManager.entriesCount() > 0) { + info.append(", Social: ").append(LinksManager.socialManager.entriesCount()) } } info.append(", Recap: ").append(Recap.recaps.size).append(']') diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Me.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Me.kt index f1098fa..bbd5479 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Me.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Me.kt @@ -45,7 +45,7 @@ class Me : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.bot().sendIRC().action(channel, args) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Modules.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Modules.kt index 82a0b06..456fa7f 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Modules.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Modules.kt @@ -45,7 +45,7 @@ class Modules(private val modules: List) : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { if (modules.isEmpty()) { event.respondPrivateMessage("There are no enabled modules.") } else { diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Msg.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Msg.kt index 8bd4b37..a758996 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Msg.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Msg.kt @@ -48,7 +48,7 @@ class Msg : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { val msg = args.split(" ", limit = 2) if (args.length > 2) { event.bot().sendIRC().message(msg[0], msg[1]) diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Nick.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Nick.kt index c864763..e4cdf34 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Nick.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Nick.kt @@ -45,7 +45,7 @@ class Nick : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.bot().sendIRC().changeNick(args) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Say.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Say.kt index c03a221..0141206 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Say.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Say.kt @@ -45,7 +45,7 @@ class Say : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.bot().sendIRC().message(channel, args) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Versions.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Versions.kt index 272263b..9513aef 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/Versions.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/Versions.kt @@ -53,7 +53,7 @@ class Versions : AbstractCommand() { override val isVisible = true override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.sendList(allVersions, 1) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Comment.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Comment.kt index 10916de..e60955a 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Comment.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Comment.kt @@ -66,8 +66,8 @@ class Comment : AbstractCommand() { 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] + if (entryIndex < LinksManager.entries.links.size && LinksManager.isUpToDate(event)) { + val entry: EntryLink = LinksManager.entries.links[entryIndex] val commentIndex = cmds[1].toInt() - 1 if (commentIndex < entry.comments.size) { when (val cmd = cmds[2].trim()) { @@ -87,7 +87,7 @@ class Comment : AbstractCommand() { override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean { if (super.helpResponse(channel, topic, event)) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { event.sendMessage("To change a comment's author:") event.sendMessage(helpFormat("${Constants.LINK_CMD}1.1:?")) } @@ -108,11 +108,11 @@ class Comment : AbstractCommand() { commentIndex: Int, event: GenericMessageEvent ) { - if (isChannelOp(channel, event) && cmd.length > 1) { + if (event.isChannelOp(channel) && cmd.length > 1) { val comment = entry.getComment(commentIndex) comment.nick = cmd.substring(1) event.sendMessage(buildComment(entryIndex, commentIndex, comment)) - LinksMgr.entries.save() + LinksManager.entries.save() } else { event.sendMessage("Please ask a channel op to change the author of this comment for you.") } @@ -125,10 +125,10 @@ class Comment : AbstractCommand() { commentIndex: Int, event: GenericMessageEvent ) { - if (isChannelOp(channel, event) || event.user.nick == entry.getComment(commentIndex).nick) { + if (event.isChannelOp(channel) || event.user.nick == entry.getComment(commentIndex).nick) { entry.deleteComment(commentIndex) event.sendMessage("Comment ${entryIndex.toLinkLabel()}.${commentIndex + 1} removed.") - LinksMgr.entries.save() + LinksManager.entries.save() } else { event.sendMessage("Please ask a channel op to delete this comment for you.") } @@ -143,7 +143,7 @@ class Comment : AbstractCommand() { ) { entry.setComment(commentIndex, cmd, event.user.nick) event.sendMessage(buildComment(entryIndex, commentIndex, entry.getComment(commentIndex))) - LinksMgr.entries.save() + LinksManager.entries.save() } private fun showComment(entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent) { diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgr.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManager.kt similarity index 96% rename from src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgr.kt rename to src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManager.kt index 58efd89..d0c9769 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgr.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManager.kt @@ -1,5 +1,5 @@ /* - * LinksMgr.kt + * LinksManager.kt * * Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net) * All rights reserved. @@ -45,12 +45,12 @@ import net.thauvin.erik.mobibot.entries.Entries import net.thauvin.erik.mobibot.entries.EntriesUtils.buildLink import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel import net.thauvin.erik.mobibot.entries.EntryLink -import net.thauvin.erik.mobibot.modules.Twitter +import net.thauvin.erik.mobibot.social.SocialManager import org.jsoup.Jsoup import org.pircbotx.hooks.types.GenericMessageEvent import java.io.IOException -class LinksMgr : AbstractCommand() { +class LinksManager : AbstractCommand() { private val defaultTags: MutableList = mutableListOf() private val keywords: MutableList = mutableListOf() @@ -83,10 +83,10 @@ class LinksMgr : AbstractCommand() { val pinboard = Pinboard() /** - * Twitter handler. + * Social Manager handler. */ @JvmField - val twitter = Twitter() + val socialManager = SocialManager() /** * Let the user know if the entries are too old to be modified. @@ -140,8 +140,8 @@ class LinksMgr : AbstractCommand() { pinboard.addPin(event.bot().serverHostname, entry) - // Queue link for posting to Twitter. - twitter.queueEntry(index) + // Queue link for posting to social media. + socialManager.queueEntry(index) entries.save() diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Posting.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Posting.kt index bc91b97..a0224b9 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Posting.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Posting.kt @@ -39,7 +39,7 @@ 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.commands.links.LinksManager.Companion.entries import net.thauvin.erik.mobibot.entries.EntriesUtils import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel import net.thauvin.erik.mobibot.entries.EntryLink @@ -71,7 +71,7 @@ class Posting : AbstractCommand() { val cmd = cmds[1].trim() if (cmd.isBlank()) { showEntry(entryIndex, event) // L1: - } else if (LinksMgr.isUpToDate(event)) { + } else if (LinksManager.isUpToDate(event)) { if (cmd == "-") { removeEntry(channel, entryIndex, event) // L1:- } else { @@ -102,7 +102,7 @@ class Posting : AbstractCommand() { 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) + LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry) event.sendMessage(EntriesUtils.buildLink(entryIndex, entry)) entries.save() } @@ -110,12 +110,12 @@ class Posting : AbstractCommand() { 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)) { + if (entry.login == event.user.login || event.isChannelOp(channel)) { val link = cmd.substring(1) - if (link.matches(LinksMgr.LINK_MATCH.toRegex())) { + if (link.matches(LinksManager.LINK_MATCH.toRegex())) { val oldLink = entry.link entry.link = link - LinksMgr.pinboard.updatePin(event.bot().serverHostname, oldLink, entry) + LinksManager.pinboard.updatePin(event.bot().serverHostname, oldLink, entry) event.sendMessage(EntriesUtils.buildLink(entryIndex, entry)) entries.save() } @@ -123,11 +123,11 @@ class Posting : AbstractCommand() { } private fun changeAuthor(channel: String, cmd: String, index: Int, event: GenericMessageEvent) { - if (isChannelOp(channel, event)) { + if (event.isChannelOp(channel)) { if (cmd.length > 1) { val entry: EntryLink = entries.links[index] entry.nick = cmd.substring(1) - LinksMgr.pinboard.updatePin(event.bot().serverHostname, entry.link, entry) + LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry) event.sendMessage(EntriesUtils.buildLink(index, entry)) entries.save() } @@ -138,9 +138,9 @@ class Posting : AbstractCommand() { 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) + if (entry.login == event.user.login || event.isChannelOp(channel)) { + LinksManager.pinboard.deletePin(entry) + LinksManager.socialManager.removeEntry(index) entries.links.removeAt(index) event.sendMessage("Entry ${index.toLinkLabel()} removed.") entries.save() diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Tags.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Tags.kt index 842866e..8146e88 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Tags.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/Tags.kt @@ -60,15 +60,15 @@ class Tags : AbstractCommand() { 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)) { + if (index < LinksManager.entries.links.size && LinksManager.isUpToDate(event)) { val cmd = cmds[1].trim() - val entry: EntryLink = LinksMgr.entries.links[index] + val entry: EntryLink = LinksManager.entries.links[index] if (cmd.isNotEmpty()) { - if (entry.login == event.user.login || isChannelOp(channel, event)) { + if (entry.login == event.user.login || event.isChannelOp(channel)) { entry.setTags(cmd) - LinksMgr.pinboard.updatePin(event.bot().serverHostname, entry.link, entry) + LinksManager.pinboard.updatePin(event.bot().serverHostname, entry.link, entry) event.sendMessage(EntriesUtils.buildTags(index, entry)) - LinksMgr.entries.save() + LinksManager.entries.save() } else { event.sendMessage("Please ask a channel op to change the tags for you.") } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/View.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/View.kt index dd22c0e..ee817a0 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/View.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/links/View.kt @@ -38,7 +38,7 @@ 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.commands.links.LinksManager.Companion.entries import net.thauvin.erik.mobibot.entries.EntriesUtils import net.thauvin.erik.mobibot.entries.EntryLink import org.pircbotx.hooks.events.PrivateMessageEvent diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/Tell.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/Tell.kt index a581cd5..f591283 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/Tell.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/Tell.kt @@ -81,7 +81,7 @@ class Tell(private val serialObject: String) : AbstractCommand() { * Cleans the messages queue. */ private fun clean(): Boolean { - return TellMessagesMgr.clean(messages, maxDays.toLong()) + return TellManager.clean(messages, maxDays.toLong()) } override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) { @@ -89,7 +89,7 @@ class Tell(private val serialObject: String) : AbstractCommand() { 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) { + if (event.isChannelOp(channel) && "${View.VIEW_CMD} $TELL_ALL_KEYWORD" == args) { viewAll(event) } else { viewMessages(event) @@ -120,7 +120,7 @@ class Tell(private val serialObject: String) : AbstractCommand() { } else { if (messages.removeIf { it.id == id && - (it.sender.equals(event.user.nick, true) || isChannelOp(channel, event)) + (it.sender.equals(event.user.nick, true) || event.isChannelOp(channel)) }) { save() event.sendMessage("The message was deleted from the queue.") @@ -167,7 +167,7 @@ class Tell(private val serialObject: String) : AbstractCommand() { * Saves the messages queue. */ private fun save() { - TellMessagesMgr.save(serialObject, messages) + TellManager.save(serialObject, messages) } /** @@ -291,7 +291,7 @@ class Tell(private val serialObject: String) : AbstractCommand() { initProperties(MAX_DAYS_PROP, MAX_SIZE_PROP) // Load the message queue - messages.addAll(TellMessagesMgr.load(serialObject)) + messages.addAll(TellManager.load(serialObject)) if (clean()) { save() } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt b/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellManager.kt similarity index 95% rename from src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt rename to src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellManager.kt index 72897e6..3c1f6b5 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/commands/tell/TellManager.kt @@ -1,5 +1,5 @@ /* - * TellMessagesMgr.kt + * TellManager.kt * * Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net) * All rights reserved. @@ -41,8 +41,8 @@ import java.time.LocalDateTime /** * The Tell Messages Manager. */ -object TellMessagesMgr { - private val logger: Logger = LoggerFactory.getLogger(TellMessagesMgr::class.java) +object TellManager { + private val logger: Logger = LoggerFactory.getLogger(TellManager::class.java) /** * Cleans the messages queue. diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/entries/Entries.kt b/src/main/kotlin/net/thauvin/erik/mobibot/entries/Entries.kt index a775168..a81a737 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/entries/Entries.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/entries/Entries.kt @@ -45,11 +45,11 @@ class Entries( var lastPubDate = today() fun load() { - lastPubDate = FeedsMgr.loadFeed(this) + lastPubDate = FeedsManager.loadFeed(this) } fun save() { lastPubDate = today() - FeedsMgr.saveFeed(this) + FeedsManager.saveFeed(this) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/entries/EntryLink.kt b/src/main/kotlin/net/thauvin/erik/mobibot/entries/EntryLink.kt index 9177ebe..b70fa9d 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/entries/EntryLink.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/entries/EntryLink.kt @@ -33,7 +33,7 @@ package net.thauvin.erik.mobibot.entries import com.rometools.rome.feed.synd.SyndCategory import com.rometools.rome.feed.synd.SyndCategoryImpl -import net.thauvin.erik.mobibot.commands.links.LinksMgr +import net.thauvin.erik.mobibot.commands.links.LinksManager import java.io.Serializable import java.util.Calendar import java.util.Date @@ -169,7 +169,7 @@ class EntryLink( * Sets the tags. */ fun setTags(tags: String) { - setTags(tags.split(LinksMgr.TAG_MATCH)) + setTags(tags.split(LinksManager.TAG_MATCH)) } /** diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsMgr.kt b/src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsManager.kt similarity index 99% rename from src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsMgr.kt rename to src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsManager.kt index 9060146..32ba16d 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsMgr.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/entries/FeedsManager.kt @@ -1,5 +1,5 @@ /* - * FeedsMgr.kt + * FeedsManager.kt * * Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net) * All rights reserved. @@ -55,9 +55,9 @@ import kotlin.io.path.exists /** * Manages the RSS feeds. */ -class FeedsMgr private constructor() { +class FeedsManager private constructor() { companion object { - private val logger: Logger = LoggerFactory.getLogger(FeedsMgr::class.java) + private val logger: Logger = LoggerFactory.getLogger(FeedsManager::class.java) // The file containing the current entries. private const val currentXml = "current.xml" diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt new file mode 100644 index 0000000..858676e --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt @@ -0,0 +1,150 @@ +/* + * Mastodon.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.modules + +import net.thauvin.erik.mobibot.Utils +import net.thauvin.erik.mobibot.Utils.prefixIfMissing +import net.thauvin.erik.mobibot.entries.EntryLink +import net.thauvin.erik.mobibot.social.SocialModule +import org.json.JSONException +import org.json.JSONObject +import org.json.JSONWriter +import java.io.IOException +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse + +class Mastodon : SocialModule() { + override val name = "Mastodon" + + override val handle: String? + get() = properties[HANDLE_PROP] + + override val isAutoPost: Boolean + get() = isEnabled && properties[Twitter.AUTO_POST_PROP].toBoolean() + + override val isValidProperties: Boolean + get() { + for (s in propertyKeys) { + if (AUTO_POST_PROP != s && HANDLE_PROP != s && properties[s].isNullOrBlank()) { + return false + } + } + return true + } + + /** + * Formats the entry for posting. + */ + override fun formatEntry(entry: EntryLink): String { + return "${entry.title} via ${entry.nick} on ${entry.channel}\n\n${entry.link}" + } + + /** + * Posts on Mastodon. + */ + @Throws(ModuleException::class) + override fun post(message: String, isDm: Boolean): String { + return toot( + apiKey = properties[ACCESS_TOKEN_PROP], + instance = properties[INSTANCE_PROP], + handle = handle, + message = message, + isDm = isDm + ) + } + + companion object { + // Property keys + const val ACCESS_TOKEN_PROP = "mastodon-access-token" + const val AUTO_POST_PROP = "mastodon-auto-post" + const val HANDLE_PROP = "mastodon-handle" + const val INSTANCE_PROP = "mastodon-instance" + + private const val MASTODON_CMD = "mastodon" + + /** + * Toots on Mastodon. + */ + @JvmStatic + @Throws(ModuleException::class) + fun toot(apiKey: String?, instance: String?, handle: String?, message: String, isDm: Boolean): String { + val request = HttpRequest.newBuilder() + .uri(URI.create("https://$instance/api/v1/statuses")) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer $apiKey") + .POST( + HttpRequest.BodyPublishers.ofString( + JSONWriter.valueToString( + if (isDm) { + mapOf("status" to "${handle?.prefixIfMissing('@')} $message", "visibility" to "direct") + } else { + mapOf("status" to message) + } + ) + ) + ) + .build() + try { + val response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()) + if (response.statusCode() == 200) { + return try { + val jsonResponse = JSONObject(response.body()) + if (isDm) { + jsonResponse.getString("content") + } else { + "Your message was posted to ${jsonResponse.getString("url")}" + } + } catch (e: JSONException) { + throw ModuleException("mastodonPost($message)", "A JSON error has occurred: ${e.message}", e) + } + } else { + throw IOException("Status Code: " + response.statusCode()) + } + } catch (e: IOException) { + throw ModuleException("mastodonPost($message)", "An IO error has occurred: ${e.message}", e) + } catch (e: InterruptedException) { + throw ModuleException("mastodonPost($message)", "An error has occurred: ${e.message}", e) + } + } + } + + init { + commands.add(MASTODON_CMD) + help.add("To post to Mastodon:") + help.add(Utils.helpFormat("%c $MASTODON_CMD ")) + properties[AUTO_POST_PROP] = "false" + initProperties(ACCESS_TOKEN_PROP, HANDLE_PROP, INSTANCE_PROP) + } +} diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/modules/Twitter.kt b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Twitter.kt index 145f01f..91cca2a 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/modules/Twitter.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/modules/Twitter.kt @@ -31,57 +31,27 @@ */ package net.thauvin.erik.mobibot.modules -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import net.thauvin.erik.mobibot.Constants -import net.thauvin.erik.mobibot.TwitterTimer import net.thauvin.erik.mobibot.Utils.helpFormat -import net.thauvin.erik.mobibot.commands.links.LinksMgr -import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel -import org.pircbotx.hooks.types.GenericMessageEvent -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import net.thauvin.erik.mobibot.entries.EntryLink +import net.thauvin.erik.mobibot.social.SocialModule import twitter4j.TwitterException -import java.util.Timer /** * The Twitter module. */ -class Twitter : ThreadedModule() { - private val logger: Logger = LoggerFactory.getLogger(Twitter::class.java) - - private val timer = Timer(true) - - // Twitter auto-posts. - private val entries: MutableSet = HashSet() - +class Twitter : SocialModule() { override val name = "Twitter" - /** - * Add an entry to be posted on Twitter. - */ - private fun addEntry(index: Int) { - entries.add(index) - } - - fun entriesCount(): Int { - return entries.size - } - - private val handle: String? + override val handle: String? get() = properties[HANDLE_PROP] - private fun hasEntry(index: Int): Boolean { - return entries.contains(index) - } - - val isAutoPost: Boolean - get() = isEnabled && properties[AUTOPOST_PROP].toBoolean() + override val isAutoPost: Boolean + get() = isEnabled && properties[AUTO_POST_PROP].toBoolean() override val isValidProperties: Boolean get() { for (s in propertyKeys) { - if (AUTOPOST_PROP != s && HANDLE_PROP != s && properties[s].isNullOrBlank()) { + if (AUTO_POST_PROP != s && HANDLE_PROP != s && properties[s].isNullOrBlank()) { return false } } @@ -89,105 +59,31 @@ class Twitter : ThreadedModule() { } /** - * Send a notification to the registered Twitter handle. + * Formats the entry for posting. */ - fun notification(msg: String) { - if (isEnabled && !handle.isNullOrBlank()) { - runBlocking { - launch { - try { - post(message = msg, isDm = true) - if (logger.isDebugEnabled) logger.debug("Notified @$handle: $msg") - } catch (e: ModuleException) { - if (logger.isWarnEnabled) logger.warn("Failed to notify @$handle: $msg", e) - } - } - } - } + override fun formatEntry(entry: EntryLink): String { + return "${entry.title} ${entry.link} via ${entry.nick} on ${entry.channel}" } /** * Posts on Twitter. */ @Throws(ModuleException::class) - fun post(handle: String = "${properties[HANDLE_PROP]}", message: String, isDm: Boolean): String { - return twitterPost( - properties[CONSUMER_KEY_PROP], - properties[CONSUMER_SECRET_PROP], - properties[TOKEN_PROP], - properties[TOKEN_SECRET_PROP], - handle, - message, - isDm + 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 ) } - /** - * Post an entry to twitter. - */ - fun postEntry(index: Int) { - if (isAutoPost && hasEntry(index) && LinksMgr.entries.links.size >= index) { - val entry = LinksMgr.entries.links[index] - val msg = "${entry.title} ${entry.link} via ${entry.nick} on ${entry.channel}" - runBlocking { - launch { - try { - if (logger.isDebugEnabled) { - logger.debug("Posting {} to Twitter.", index.toLinkLabel()) - } - post(message = msg, isDm = false) - } catch (e: ModuleException) { - if (logger.isWarnEnabled) logger.warn("Failed to post entry on Twitter.", e) - } - } - } - removeEntry(index) - } - } - - fun queueEntry(index: Int) { - if (isAutoPost) { - addEntry(index) - if (logger.isDebugEnabled) { - logger.debug("Scheduling {} for posting on Twitter.", index.toLinkLabel()) - } - timer.schedule(TwitterTimer(this, index), Constants.TIMER_DELAY * 60L * 1000L) - } - } - - fun removeEntry(index: Int) { - entries.remove(index) - } - - /** - * Posts to twitter. - */ - override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) { - try { - event.respond(post(event.user.nick, "$args (by ${event.user.nick} on $channel)", false)) - } catch (e: ModuleException) { - if (logger.isWarnEnabled) logger.warn(e.debugMessage, e) - e.message?.let { - event.respond(it) - } - } - } - - /** - * Post all the entries to Twitter on shutdown. - */ - fun shutdown() { - timer.cancel() - if (isAutoPost) { - for (index in entries) { - postEntry(index) - } - } - } - companion object { // Property keys - const val AUTOPOST_PROP = "twitter-auto-post" + 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" @@ -198,11 +94,11 @@ class Twitter : ThreadedModule() { private const val TWITTER_CMD = "twitter" /** - * Posts on Twitter. + * Tweets on Twitter. */ @JvmStatic @Throws(ModuleException::class) - fun twitterPost( + fun tweet( consumerKey: String?, consumerSecret: String?, token: String?, @@ -236,7 +132,7 @@ class Twitter : ThreadedModule() { commands.add(TWITTER_CMD) help.add("To post to Twitter:") help.add(helpFormat("%c $TWITTER_CMD ")) - properties[AUTOPOST_PROP] = "false" + properties[AUTO_POST_PROP] = "false" initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP) } } diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt new file mode 100644 index 0000000..c991140 --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt @@ -0,0 +1,117 @@ +/* + * SocialManager.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.social + +import net.thauvin.erik.mobibot.Addons +import net.thauvin.erik.mobibot.Constants +import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.Timer + +/** + * Social Manager. + */ +class SocialManager { + private val entries: MutableSet = HashSet() + private val logger: Logger = LoggerFactory.getLogger(SocialManager::class.java) + private val modules = ArrayList() + private val timer = Timer(true) + + /** + * Adds social modules. + */ + fun add(addons: Addons, vararg modules: SocialModule) { + modules.forEach { + if (addons.add(it)) { + this.modules.add(it) + } + } + } + + /** + * Returns the number of entries. + */ + fun entriesCount(): Int = entries.size + + /** + * Sends a social notification (dm, etc.) + */ + fun notification(msg: String) { + modules.forEach { + it.notification(msg) + } + } + + /** + * Posts to social media. + */ + fun postEntry(index: Int) { + if (entries.contains(index)) { + modules.forEach { + it.postEntry(index) + } + entries.remove(index) + } + } + + /** + * Queues an entry for posting to social media. + */ + fun queueEntry(index: Int) { + if (modules.isNotEmpty()) { + entries.add(index) + if (logger.isDebugEnabled) { + logger.debug("Scheduling {} for posting on social media.", index.toLinkLabel()) + } + timer.schedule(SocialTimer(this, index), Constants.TIMER_DELAY * 60L * 1000L) + } + } + + /** + * Removes entries from queue. + */ + fun removeEntry(index: Int) { + entries.remove(index) + } + + /** + * Posts all entries on shutdown. + */ + fun shutdown() { + timer.cancel() + for (index in entries) { + postEntry(index) + } + } +} diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt new file mode 100644 index 0000000..c15dd55 --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt @@ -0,0 +1,107 @@ +/* + * SocialModule.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.social + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import net.thauvin.erik.mobibot.commands.links.LinksManager +import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel +import net.thauvin.erik.mobibot.entries.EntryLink +import net.thauvin.erik.mobibot.modules.ModuleException +import net.thauvin.erik.mobibot.modules.ThreadedModule +import org.pircbotx.hooks.types.GenericMessageEvent +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +abstract class SocialModule : ThreadedModule() { + private val logger: Logger = LoggerFactory.getLogger(SocialManager::class.java) + + abstract val handle: String? + abstract val isAutoPost: Boolean + + abstract fun formatEntry(entry: EntryLink): String + + /** + * Sends a DM. + */ + fun notification(msg: String) { + if (isEnabled && !handle.isNullOrBlank()) { + runBlocking { + launch { + try { + post(message = msg, isDm = true) + if (logger.isDebugEnabled) logger.debug("Notified $handle on $name: $msg") + } catch (e: ModuleException) { + if (logger.isWarnEnabled) logger.warn("Failed to notify $handle on $name: $msg", e) + } + } + } + } + } + + abstract fun post(message: String, isDm: Boolean): String + + /** + * Post entry to social media. + */ + fun postEntry(index: Int) { + if (isAutoPost && LinksManager.entries.links.size >= index) { + runBlocking { + launch { + try { + if (logger.isDebugEnabled) { + logger.debug("Posting {} to $name.", index.toLinkLabel()) + } + post(message = formatEntry(LinksManager.entries.links[index]), isDm = false) + } catch (e: ModuleException) { + if (logger.isWarnEnabled) logger.warn( + "Failed to post entry ${index.toLinkLabel()} on $name.", + e + ) + } + } + } + } + } + + override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent) { + try { + event.respond(post("$args (by ${event.user.nick} on $channel)", false)) + } catch (e: ModuleException) { + if (logger.isWarnEnabled) logger.warn(e.debugMessage, e) + e.message?.let { + event.respond(it) + } + } + } +} diff --git a/src/main/kotlin/net/thauvin/erik/mobibot/TwitterTimer.kt b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialTimer.kt similarity index 88% rename from src/main/kotlin/net/thauvin/erik/mobibot/TwitterTimer.kt rename to src/main/kotlin/net/thauvin/erik/mobibot/social/SocialTimer.kt index 09bfa46..3edb06b 100644 --- a/src/main/kotlin/net/thauvin/erik/mobibot/TwitterTimer.kt +++ b/src/main/kotlin/net/thauvin/erik/mobibot/social/SocialTimer.kt @@ -1,5 +1,5 @@ /* - * TwitterTimer.kt + * SocialTimer.kt * * Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net) * All rights reserved. @@ -30,13 +30,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.thauvin.erik.mobibot +package net.thauvin.erik.mobibot.social -import net.thauvin.erik.mobibot.modules.Twitter import java.util.TimerTask -class TwitterTimer(private var twitter: Twitter, private var index: Int) : TimerTask() { +class SocialTimer(private var socialManager: SocialManager, private var index: Int) : TimerTask() { override fun run() { - twitter.postEntry(index) + socialManager.postEntry(index) } } diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/LocalProperties.kt b/src/test/kotlin/net/thauvin/erik/mobibot/LocalProperties.kt index 9249006..7dba11a 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/LocalProperties.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/LocalProperties.kt @@ -33,6 +33,8 @@ package net.thauvin.erik.mobibot import org.testng.annotations.BeforeSuite import java.io.IOException +import java.net.InetAddress +import java.net.UnknownHostException import java.nio.file.Files import java.nio.file.Paths import java.util.Properties @@ -56,6 +58,15 @@ open class LocalProperties { companion object { private val localProps = Properties() + fun getHostName(): String { + val ciName = System.getenv("CI_NAME") + return ciName ?: try { + InetAddress.getLocalHost().hostName + } catch (ignore: UnknownHostException) { + "Unknown Host" + } + } + fun getProperty(key: String): String { return if (localProps.containsKey(key)) { localProps.getProperty(key) diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgrTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManagerTest.kt similarity index 75% rename from src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgrTest.kt rename to src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManagerTest.kt index adcf563..fa4a099 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksMgrTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/LinksManagerTest.kt @@ -41,31 +41,34 @@ import assertk.assertions.size import net.thauvin.erik.mobibot.Constants import org.testng.annotations.Test -class LinksMgrTest { - private val linksMgr = LinksMgr() +class LinksManagerTest { + private val linksManager = LinksManager() @Test(groups = ["commands", "links"]) fun fetchTitle() { - assertThat(linksMgr.fetchTitle("https://erik.thauvin.net/"), "fetchTitle(Erik)").contains("Erik's Weblog") - assertThat(linksMgr.fetchTitle("https://www.google.com/foo"), "fetchTitle(Foo)").isEqualTo(Constants.NO_TITLE) + assertThat(linksManager.fetchTitle("https://erik.thauvin.net/"), "fetchTitle(Erik)").contains("Erik's Weblog") + assertThat( + linksManager.fetchTitle("https://www.google.com/foo"), + "fetchTitle(Foo)" + ).isEqualTo(Constants.NO_TITLE) } @Test(groups = ["commands", "links"]) fun testMatches() { - assertThat(linksMgr.matches("https://www.example.com/"), "matches(url)").isTrue() - assertThat(linksMgr.matches("HTTP://erik.thauvin.net/blog/ Erik's Weblog"), "matches(HTTP)").isTrue() + assertThat(linksManager.matches("https://www.example.com/"), "matches(url)").isTrue() + assertThat(linksManager.matches("HTTP://erik.thauvin.net/blog/ Erik's Weblog"), "matches(HTTP)").isTrue() } @Test(groups = ["commands", "links"]) fun matchTagKeywordsTest() { - linksMgr.setProperty(LinksMgr.KEYWORDS_PROP, "key1 key2,key3") + linksManager.setProperty(LinksManager.KEYWORDS_PROP, "key1 key2,key3") val tags = mutableListOf() - linksMgr.matchTagKeywords("Test title with key2", tags) + linksManager.matchTagKeywords("Test title with key2", tags) assertThat(tags, "tags").contains("key2") tags.clear() - linksMgr.matchTagKeywords("Test key3 title with key1", tags) + linksManager.matchTagKeywords("Test key3 title with key1", tags) assertThat(tags, "tags(key1, key3)").all { contains("key1") contains("key3") diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/ViewTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/ViewTest.kt index 7ef0184..e7c07f0 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/ViewTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/commands/links/ViewTest.kt @@ -45,7 +45,7 @@ class ViewTest { val view = View() for (i in 1..10) { - LinksMgr.entries.links.add( + LinksManager.entries.links.add( EntryLink( "https://www.example.com/$i", "Example $i", @@ -98,11 +98,11 @@ class ViewTest { } assertThat(view.parseArgs(""), "parseArgs()").all { - prop(Pair::first).isEqualTo(LinksMgr.entries.links.size - View.MAX_ENTRIES) + prop(Pair::first).isEqualTo(LinksManager.entries.links.size - View.MAX_ENTRIES) prop(Pair::second).isEqualTo("") } - LinksMgr.entries.links.clear() + LinksManager.entries.links.clear() assertThat(view.parseArgs("4"), "parseArgs(4)").all { prop(Pair::first).isEqualTo(0) diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgrTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgrTest.kt index fe83db7..a07eb68 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgrTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgrTest.kt @@ -60,7 +60,7 @@ class TellMessagesMgrTest { @BeforeClass fun saveTest() { - TellMessagesMgr.save(testFile.toAbsolutePath().toString(), testMessages) + TellManager.save(testFile.toAbsolutePath().toString(), testMessages) assertThat(testFile.fileSize()).isGreaterThan(0) } @@ -75,14 +75,14 @@ class TellMessagesMgrTest { queued = LocalDateTime.now().minusDays(maxDays) }) val size = testMessages.size - assertThat(TellMessagesMgr.clean(testMessages, maxDays + 2), "clean(maxDays=${maxDays + 2})").isFalse() - assertThat(TellMessagesMgr.clean(testMessages, maxDays), "clean(maxDays=$maxDays)").isTrue() + assertThat(TellManager.clean(testMessages, maxDays + 2), "clean(maxDays=${maxDays + 2})").isFalse() + assertThat(TellManager.clean(testMessages, maxDays), "clean(maxDays=$maxDays)").isTrue() assertThat(testMessages, "testMessages").size().isEqualTo(size - 1) } @Test(groups = ["commands", "tell"]) fun loadTest() { - val messages = TellMessagesMgr.load(testFile.toAbsolutePath().toString()) + val messages = TellManager.load(testFile.toAbsolutePath().toString()) for (i in messages.indices) { assertThat(messages).index(i).all { prop(TellMessage::sender).isEqualTo(testMessages[i].sender) diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/entries/FeedMgrTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/entries/FeedMgrTest.kt index a92f2b1..68be3f7 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/entries/FeedMgrTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/entries/FeedMgrTest.kt @@ -65,7 +65,7 @@ class FeedMgrTest { @Test(groups = ["entries"]) fun testFeedMgr() { // Load the feed - assertThat(FeedsMgr.loadFeed(entries), "loadFeed()").isEqualTo("2021-10-31") + assertThat(FeedsManager.loadFeed(entries), "loadFeed()").isEqualTo("2021-10-31") assertThat(entries.links, "entries.links").size().isEqualTo(2) entries.links.forEachIndexed { i, entryLink -> @@ -101,7 +101,7 @@ class FeedMgrTest { val backlogFile = Paths.get("${entries.logsDir}${today()}.xml") // Save the feed - FeedsMgr.saveFeed(entries, currentFile.name) + FeedsManager.saveFeed(entries, currentFile.name) assertThat(currentFile, "currentFile").exists() assertThat(backlogFile, "backlogFile").exists() @@ -110,7 +110,7 @@ class FeedMgrTest { // Load the test feed entries.links.clear() - FeedsMgr.loadFeed(entries, currentFile.name) + FeedsManager.loadFeed(entries, currentFile.name) entries.links.forEachIndexed { i, entryLink -> assertThat(entryLink.title, "entryLink.title[${i + 1}]").isEqualTo("Example ${i + 1}") diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/MastodonTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/MastodonTest.kt new file mode 100644 index 0000000..74b5a9a --- /dev/null +++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/MastodonTest.kt @@ -0,0 +1,56 @@ +/* + * MastodonTest.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.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 + +class MastodonTest : LocalProperties() { + @Test(groups = ["modules"]) + @Throws(ModuleException::class) + fun testToot() { + val msg = "Testing Mastodon API from ${getHostName()}" + assertThat { + toot( + getProperty(Mastodon.ACCESS_TOKEN_PROP), + getProperty(Mastodon.INSTANCE_PROP), + getProperty(Mastodon.HANDLE_PROP), + msg, + true + ) + }.isSuccess().contains(msg) + } +} diff --git a/src/test/kotlin/net/thauvin/erik/mobibot/modules/TwitterTest.kt b/src/test/kotlin/net/thauvin/erik/mobibot/modules/TwitterTest.kt index dd994a3..126fb4c 100644 --- a/src/test/kotlin/net/thauvin/erik/mobibot/modules/TwitterTest.kt +++ b/src/test/kotlin/net/thauvin/erik/mobibot/modules/TwitterTest.kt @@ -35,31 +35,19 @@ 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.twitterPost +import net.thauvin.erik.mobibot.modules.Twitter.Companion.tweet import org.testng.annotations.Test -import java.net.InetAddress -import java.net.UnknownHostException /** * The `TwitterTest` class. */ class TwitterTest : LocalProperties() { - private val ci: String - get() { - val ciName = System.getenv("CI_NAME") - return ciName ?: try { - InetAddress.getLocalHost().hostName - } catch (ignore: UnknownHostException) { - "Unknown Host" - } - } - @Test(groups = ["modules"]) @Throws(ModuleException::class) - fun testPostTwitter() { - val msg = "Testing Twitter API from $ci" + fun testTweet() { + val msg = "Testing Twitter API from ${getHostName()}" assertThat { - twitterPost( + tweet( getProperty(Twitter.CONSUMER_KEY_PROP), getProperty(Twitter.CONSUMER_SECRET_PROP), getProperty(Twitter.TOKEN_PROP), diff --git a/version.properties b/version.properties index 789278e..d91b88d 100644 --- a/version.properties +++ b/version.properties @@ -1,9 +1,9 @@ #Generated by the Semver Plugin for Gradle -#Sun Dec 04 17:34:43 PST 2022 -version.buildmeta=799 +#Mon Dec 05 21:57:24 PST 2022 +version.buildmeta=814 version.major=0 version.minor=8 version.patch=0 version.prerelease=rc version.project=mobibot -version.semver=0.8.0-rc+799 +version.semver=0.8.0-rc+814 diff --git a/website/index.html b/website/index.html index 6a2d139..721e840 100644 --- a/website/index.html +++ b/website/index.html @@ -81,9 +81,9 @@
  • Performing Google searches
    mobibot: google mobitopia on irc
  • -
  • Getting answers from Wolfram Alpha +
  • Getting answers from Wolfram Alpha and ChatGPT
    mobibot: wolfram days until christmas
    -
    mobibot: wolfram 1 gallon to liter
    +
    mobibot: chatgpt explain quantum computing in simple terms
  • Displaying weather information
    mobibot: weather san francisco
    @@ -116,13 +116,13 @@
  • Random jokes from Sv443's JokeAPI
    mobibot: joke
  • -
  • Rolling dice or Playing war and rock paper scissors +
  • Playing dice, war or rock paper scissors
    mobibot: dice
    mobibot: war
    mobibot: paper
    mobibot: rock
  • -
  • Posting to Twitter
  • +
  • Posting to Twitter and Mastodon
  • Some of the internal features include RSS feed backlogs, rolling logs, debugging toggle and much more.