Implemented a SocialManager with support for posting to both Twitter and Mastodon
This commit is contained in:
parent
6a3caa84ce
commit
6fd59e9487
42 changed files with 642 additions and 269 deletions
4
.github/workflows/gradle.yml
vendored
4
.github/workflows/gradle.yml
vendored
|
@ -57,6 +57,10 @@ jobs:
|
||||||
TWITTER_HANDLE: ${{ secrets.TWITTER_HANDLE }}
|
TWITTER_HANDLE: ${{ secrets.TWITTER_HANDLE }}
|
||||||
TWITTER_TOKEN: ${{ secrets.TWITTER_TOKEN }}
|
TWITTER_TOKEN: ${{ secrets.TWITTER_TOKEN }}
|
||||||
TWITTER_TOKENSECRET: ${{ secrets.TWITTER_TOKENSECRET }}
|
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
|
run: ./gradlew build check --stacktrace
|
||||||
|
|
||||||
- name: SonarCloud
|
- name: SonarCloud
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<SmellBaseline>
|
<SmellBaseline>
|
||||||
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
||||||
<CurrentIssues>
|
<CurrentIssues>
|
||||||
<ID>CyclomaticComplexMethod:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
<ID>CyclomaticComplexMethod:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
||||||
<ID>CyclomaticComplexMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
<ID>CyclomaticComplexMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
||||||
<ID>LongMethod:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
<ID>LongMethod:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
||||||
<ID>LongMethod:Mobibot.kt$Mobibot.Companion$@JvmStatic @Throws(Exception::class) fun main(args: Array<String>)</ID>
|
<ID>LongMethod:Mobibot.kt$Mobibot.Companion$@JvmStatic @Throws(Exception::class) fun main(args: Array<String>)</ID>
|
||||||
<ID>LongMethod:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
<ID>LongMethod:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
||||||
<ID>LongMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
<ID>LongMethod:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
||||||
|
@ -23,13 +23,14 @@
|
||||||
<ID>MagicNumber:Info.kt$Info.Companion$30</ID>
|
<ID>MagicNumber:Info.kt$Info.Companion$30</ID>
|
||||||
<ID>MagicNumber:Info.kt$Info.Companion$365</ID>
|
<ID>MagicNumber:Info.kt$Info.Companion$365</ID>
|
||||||
<ID>MagicNumber:Info.kt$Info.Companion$7</ID>
|
<ID>MagicNumber:Info.kt$Info.Companion$7</ID>
|
||||||
|
<ID>MagicNumber:Mastodon.kt$Mastodon.Companion$200</ID>
|
||||||
<ID>MagicNumber:Mobibot.kt$Mobibot$8</ID>
|
<ID>MagicNumber:Mobibot.kt$Mobibot$8</ID>
|
||||||
<ID>MagicNumber:Modules.kt$Modules$7</ID>
|
<ID>MagicNumber:Modules.kt$Modules$7</ID>
|
||||||
|
<ID>MagicNumber:SocialManager.kt$SocialManager$1000L</ID>
|
||||||
|
<ID>MagicNumber:SocialManager.kt$SocialManager$60L</ID>
|
||||||
<ID>MagicNumber:StockQuote.kt$StockQuote.Companion$10</ID>
|
<ID>MagicNumber:StockQuote.kt$StockQuote.Companion$10</ID>
|
||||||
<ID>MagicNumber:Tell.kt$Tell$50</ID>
|
<ID>MagicNumber:Tell.kt$Tell$50</ID>
|
||||||
<ID>MagicNumber:Tell.kt$Tell$7</ID>
|
<ID>MagicNumber:Tell.kt$Tell$7</ID>
|
||||||
<ID>MagicNumber:Twitter.kt$Twitter$1000L</ID>
|
|
||||||
<ID>MagicNumber:Twitter.kt$Twitter$60L</ID>
|
|
||||||
<ID>MagicNumber:TwitterOAuth.kt$TwitterOAuth$401</ID>
|
<ID>MagicNumber:TwitterOAuth.kt$TwitterOAuth$401</ID>
|
||||||
<ID>MagicNumber:Users.kt$Users$8</ID>
|
<ID>MagicNumber:Users.kt$Users$8</ID>
|
||||||
<ID>MagicNumber:Utils.kt$Utils$200</ID>
|
<ID>MagicNumber:Utils.kt$Utils$200</ID>
|
||||||
|
@ -45,18 +46,19 @@
|
||||||
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$60</ID>
|
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$60</ID>
|
||||||
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$86.4</ID>
|
<ID>MagicNumber:WorldTime.kt$WorldTime.Companion$86.4</ID>
|
||||||
<ID>MaxLineLength:TwitterOAuth.kt$TwitterOAuth$*</ID>
|
<ID>MaxLineLength:TwitterOAuth.kt$TwitterOAuth$*</ID>
|
||||||
<ID>NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand)</ID>
|
<ID>NestedBlockDepth:Addons.kt$Addons$fun add(command: AbstractCommand): Boolean</ID>
|
||||||
<ID>NestedBlockDepth:Addons.kt$Addons$fun add(module: AbstractModule)</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?): String</ID>
|
<ID>NestedBlockDepth:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): String</ID>
|
||||||
<ID>NestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
<ID>NestedBlockDepth:Comment.kt$Comment$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
||||||
<ID>NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(query: String): Message</ID>
|
<ID>NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$@JvmStatic fun convertCurrency(query: String): Message</ID>
|
||||||
<ID>NestedBlockDepth:EntryLink.kt$EntryLink$private fun setTags(tags: List<String?>)</ID>
|
<ID>NestedBlockDepth:EntryLink.kt$EntryLink$private fun setTags(tags: List<String?>)</ID>
|
||||||
<ID>NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String</ID>
|
<ID>NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic @Throws(IOException::class, FeedException::class) fun loadFeed(entries: Entries, currentFile: String = currentXml): String</ID>
|
||||||
<ID>NestedBlockDepth:FeedsMgr.kt$FeedsMgr.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
<ID>NestedBlockDepth:FeedsManager.kt$FeedsManager.Companion$@JvmStatic fun saveFeed(entries: Entries, currentFile: String = currentXml)</ID>
|
||||||
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch$override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
|
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch$override fun run(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
|
||||||
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message></ID>
|
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message></ID>
|
||||||
<ID>NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
<ID>NestedBlockDepth:LinksManager.kt$LinksManager$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
||||||
<ID>NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
|
<ID>NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse(channel: String, cmd: String, args: String, event: GenericMessageEvent)</ID>
|
||||||
|
<ID>NestedBlockDepth:Mastodon.kt$Mastodon.Companion$@JvmStatic @Throws(ModuleException::class) fun toot(apiKey: String?, instance: String?, handle: String?, message: String, isDm: Boolean): String</ID>
|
||||||
<ID>NestedBlockDepth:Posting.kt$Posting$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
<ID>NestedBlockDepth:Posting.kt$Posting$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
||||||
<ID>NestedBlockDepth:Seen.kt$Seen$override fun commandResponse(channel: String, args: String, event: GenericMessageEvent)</ID>
|
<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<Message></ID>
|
<ID>NestedBlockDepth:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
||||||
|
@ -77,6 +79,7 @@
|
||||||
<ID>ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): String</ID>
|
<ID>ThrowsCount:ChatGpt.kt$ChatGpt.Companion$@JvmStatic @Throws(ModuleException::class) fun chat(query: String, apiKey: String?): String</ID>
|
||||||
<ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message></ID>
|
<ID>ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$@JvmStatic @Throws(ModuleException::class) fun searchGoogle( query: String, apiKey: String?, cseKey: String?, quotaUser: String = ReleaseInfo.PROJECT ): List<Message></ID>
|
||||||
<ID>ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List<Message></ID>
|
<ID>ThrowsCount:Joke.kt$Joke.Companion$@JvmStatic @Throws(ModuleException::class) fun randomJoke(): List<Message></ID>
|
||||||
|
<ID>ThrowsCount:Mastodon.kt$Mastodon.Companion$@JvmStatic @Throws(ModuleException::class) fun toot(apiKey: String?, instance: String?, handle: String?, message: String, isDm: Boolean): String</ID>
|
||||||
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message></ID>
|
||||||
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID>
|
<ID>ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject</ID>
|
||||||
<ID>ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
<ID>ThrowsCount:Weather2.kt$Weather2.Companion$@JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message></ID>
|
||||||
|
|
|
@ -41,12 +41,25 @@ tell-max-size=50
|
||||||
#twitter-token=
|
#twitter-token=
|
||||||
#twitter-tokenSecret=
|
#twitter-tokenSecret=
|
||||||
|
|
||||||
# Twitter handle to receive channel join notifications
|
# Twitter handle to receive channel join/leave notifications
|
||||||
#twitter-handle=
|
#twitter-handle=
|
||||||
|
|
||||||
# Automatically post links to twitter
|
# Automatically post links to Mastodon
|
||||||
#twitter-auto-post=true
|
#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/
|
# Create custom search engine at: https://cse.google.com/
|
||||||
# and get API key from: https://console.developers.google.com/
|
# and get API key from: https://console.developers.google.com/
|
||||||
|
@ -65,7 +78,7 @@ tell-max-size=50
|
||||||
#alphavantage-api-key=
|
#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-appid=
|
||||||
#wolfram-units=imperial
|
#wolfram-units=imperial
|
||||||
|
|
|
@ -33,7 +33,7 @@ package net.thauvin.erik.mobibot
|
||||||
|
|
||||||
import net.thauvin.erik.mobibot.Utils.notContains
|
import net.thauvin.erik.mobibot.Utils.notContains
|
||||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
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 net.thauvin.erik.mobibot.modules.AbstractModule
|
||||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||||
|
@ -46,8 +46,8 @@ import java.util.Properties
|
||||||
*/
|
*/
|
||||||
class Addons(private val props: Properties) {
|
class Addons(private val props: Properties) {
|
||||||
private val logger: Logger = LoggerFactory.getLogger(Addons::class.java)
|
private val logger: Logger = LoggerFactory.getLogger(Addons::class.java)
|
||||||
private val disabledModules = props.getProperty("disabled-modules", "").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(LinksMgr.TAG_MATCH.toRegex())
|
private val disableCommands = props.getProperty("disabled-commands", "").split(LinksManager.TAG_MATCH.toRegex())
|
||||||
|
|
||||||
val commands: MutableList<AbstractCommand> = mutableListOf()
|
val commands: MutableList<AbstractCommand> = mutableListOf()
|
||||||
val modules: MutableList<AbstractModule> = mutableListOf()
|
val modules: MutableList<AbstractModule> = mutableListOf()
|
||||||
|
@ -56,7 +56,8 @@ class Addons(private val props: Properties) {
|
||||||
/**
|
/**
|
||||||
* Add a module with properties.
|
* Add a module with properties.
|
||||||
*/
|
*/
|
||||||
fun add(module: AbstractModule) {
|
fun add(module: AbstractModule): Boolean {
|
||||||
|
var enabled = false
|
||||||
with(module) {
|
with(module) {
|
||||||
if (disabledModules.notContains(name, true)) {
|
if (disabledModules.notContains(name, true)) {
|
||||||
if (hasProperties()) {
|
if (hasProperties()) {
|
||||||
|
@ -69,6 +70,7 @@ class Addons(private val props: Properties) {
|
||||||
modules.add(this)
|
modules.add(this)
|
||||||
names.modules.add(name)
|
names.modules.add(name)
|
||||||
names.commands.addAll(commands)
|
names.commands.addAll(commands)
|
||||||
|
enabled = true
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled) {
|
if (logger.isDebugEnabled) {
|
||||||
logger.debug("Module $name is disabled.")
|
logger.debug("Module $name is disabled.")
|
||||||
|
@ -76,12 +78,14 @@ class Addons(private val props: Properties) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a command with properties.
|
* Add a command with properties.
|
||||||
*/
|
*/
|
||||||
fun add(command: AbstractCommand) {
|
fun add(command: AbstractCommand): Boolean {
|
||||||
|
var enabled = false
|
||||||
with(command) {
|
with(command) {
|
||||||
if (disableCommands.notContains(name, true)) {
|
if (disableCommands.notContains(name, true)) {
|
||||||
if (properties.isNotEmpty()) {
|
if (properties.isNotEmpty()) {
|
||||||
|
@ -98,6 +102,7 @@ class Addons(private val props: Properties) {
|
||||||
names.commands.add(name)
|
names.commands.add(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
enabled = true
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled) {
|
if (logger.isDebugEnabled) {
|
||||||
logger.debug("Command $name is disabled.")
|
logger.debug("Command $name is disabled.")
|
||||||
|
@ -105,6 +110,7 @@ class Addons(private val props: Properties) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,7 @@ import com.rometools.rome.io.XmlReader
|
||||||
import net.thauvin.erik.mobibot.Utils.green
|
import net.thauvin.erik.mobibot.Utils.green
|
||||||
import net.thauvin.erik.mobibot.Utils.helpFormat
|
import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
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.Message
|
||||||
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
import net.thauvin.erik.mobibot.msg.NoticeMessage
|
||||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||||
|
@ -50,7 +50,7 @@ import java.net.URL
|
||||||
* Reads an RSS feed.
|
* Reads an RSS feed.
|
||||||
*/
|
*/
|
||||||
class FeedReader(private val url: String, val event: GenericMessageEvent) : Runnable {
|
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.
|
* Fetches the Feed's items.
|
||||||
|
|
|
@ -61,7 +61,7 @@ import net.thauvin.erik.mobibot.commands.Say
|
||||||
import net.thauvin.erik.mobibot.commands.Users
|
import net.thauvin.erik.mobibot.commands.Users
|
||||||
import net.thauvin.erik.mobibot.commands.Versions
|
import net.thauvin.erik.mobibot.commands.Versions
|
||||||
import net.thauvin.erik.mobibot.commands.links.Comment
|
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.Posting
|
||||||
import net.thauvin.erik.mobibot.commands.links.Tags
|
import net.thauvin.erik.mobibot.commands.links.Tags
|
||||||
import net.thauvin.erik.mobibot.commands.links.View
|
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.GoogleSearch
|
||||||
import net.thauvin.erik.mobibot.modules.Joke
|
import net.thauvin.erik.mobibot.modules.Joke
|
||||||
import net.thauvin.erik.mobibot.modules.Lookup
|
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.Ping
|
||||||
import net.thauvin.erik.mobibot.modules.RockPaperScissors
|
import net.thauvin.erik.mobibot.modules.RockPaperScissors
|
||||||
import net.thauvin.erik.mobibot.modules.StockQuote
|
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.War
|
||||||
import net.thauvin.erik.mobibot.modules.Weather2
|
import net.thauvin.erik.mobibot.modules.Weather2
|
||||||
import net.thauvin.erik.mobibot.modules.WolframAlpha
|
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.sendMessage("The commands are:")
|
||||||
event.sendList(addons.names.commands, 8, isBold = true, isIndent = true)
|
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.sendMessage("The op commands are:")
|
||||||
event.sendList(addons.names.ops, 8, isBold = true, isIndent = true)
|
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?) {
|
override fun onDisconnect(event: DisconnectEvent?) {
|
||||||
event?.let {
|
event?.let {
|
||||||
with(event.getBot<PircBotX>()) {
|
with(event.getBot<PircBotX>()) {
|
||||||
LinksMgr.twitter.notification("$nick disconnected from irc://$serverHostname")
|
LinksManager.socialManager.notification("$nick disconnected from irc://$serverHostname")
|
||||||
seen.add(userChannelDao.getChannel(channel).users)
|
seen.add(userChannelDao.getChannel(channel).users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LinksMgr.twitter.shutdown()
|
LinksManager.socialManager.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrivateMessage(event: PrivateMessageEvent?) {
|
override fun onPrivateMessage(event: PrivateMessageEvent?) {
|
||||||
|
@ -199,7 +201,9 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
|
||||||
event?.user?.let { user ->
|
event?.user?.let { user ->
|
||||||
with(event.getBot<PircBotX>()) {
|
with(event.getBot<PircBotX>()) {
|
||||||
if (user.nick == nick) {
|
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)
|
seen.add(userChannelDao.getChannel(channel).users)
|
||||||
} else {
|
} else {
|
||||||
tell.send(event)
|
tell.send(event)
|
||||||
|
@ -247,7 +251,9 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
|
||||||
event?.user?.let { user ->
|
event?.user?.let { user ->
|
||||||
with(event.getBot<PircBotX>()) {
|
with(event.getBot<PircBotX>()) {
|
||||||
if (user.nick == nick) {
|
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)
|
seen.add(userChannelDao.getChannel(channel).users)
|
||||||
} else {
|
} else {
|
||||||
seen.add(user.nick)
|
seen.add(user.nick)
|
||||||
|
@ -388,7 +394,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
|
||||||
}.buildConfiguration()
|
}.buildConfiguration()
|
||||||
|
|
||||||
// Load the current entries
|
// Load the current entries
|
||||||
with(LinksMgr) {
|
with(LinksManager) {
|
||||||
entries.channel = channel
|
entries.channel = channel
|
||||||
entries.ircServer = ircServer
|
entries.ircServer = ircServer
|
||||||
entries.logsDir = logsDirPath
|
entries.logsDir = logsDirPath
|
||||||
|
@ -407,7 +413,7 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
|
||||||
addons.add(Cycle())
|
addons.add(Cycle())
|
||||||
addons.add(Die())
|
addons.add(Die())
|
||||||
addons.add(Ignore())
|
addons.add(Ignore())
|
||||||
addons.add(LinksMgr())
|
addons.add(LinksManager())
|
||||||
addons.add(Me())
|
addons.add(Me())
|
||||||
addons.add(Modules(addons.names.modules))
|
addons.add(Modules(addons.names.modules))
|
||||||
addons.add(Msg())
|
addons.add(Msg())
|
||||||
|
@ -426,11 +432,13 @@ class Mobibot(nickname: String, val channel: String, logsDirPath: String, p: Pro
|
||||||
tell = Tell("${logsDirPath}${nickname}.ser")
|
tell = Tell("${logsDirPath}${nickname}.ser")
|
||||||
addons.add(tell)
|
addons.add(tell)
|
||||||
|
|
||||||
addons.add(LinksMgr.twitter)
|
|
||||||
addons.add(Users())
|
addons.add(Users())
|
||||||
addons.add(Versions())
|
addons.add(Versions())
|
||||||
addons.add(View())
|
addons.add(View())
|
||||||
|
|
||||||
|
// Load social modules
|
||||||
|
LinksManager.socialManager.add(addons, Twitter(), Mastodon())
|
||||||
|
|
||||||
// Load the modules
|
// Load the modules
|
||||||
addons.add(Calc())
|
addons.add(Calc())
|
||||||
addons.add(ChatGpt())
|
addons.add(ChatGpt())
|
||||||
|
|
|
@ -65,6 +65,18 @@ import kotlin.io.path.fileSize
|
||||||
object Utils {
|
object Utils {
|
||||||
private val searchFlags = arrayOf("%c", "%n")
|
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.
|
* 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].
|
* Returns {@code true} if the specified user is an operator on the [channel].
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isChannelOp(channel: String, event: GenericMessageEvent): Boolean {
|
fun GenericMessageEvent.isChannelOp(channel: String): Boolean {
|
||||||
return event.bot().userChannelDao.getChannel(channel).isOp(event.user)
|
return this.bot().userChannelDao.getChannel(channel).isOp(this.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -51,7 +51,7 @@ abstract class AbstractCommand {
|
||||||
abstract fun commandResponse(channel: String, args: String, event: GenericMessageEvent)
|
abstract fun commandResponse(channel: String, args: String, event: GenericMessageEvent)
|
||||||
|
|
||||||
open fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
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) {
|
for (h in help) {
|
||||||
event.sendMessage(helpCmdSyntax(h, event.bot().nick, event is PrivateMessageEvent || !isPublic))
|
event.sendMessage(helpCmdSyntax(h, event.bot().nick, event is PrivateMessageEvent || !isPublic))
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class Cycle : AbstractCommand() {
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
with(event.bot()) {
|
with(event.bot()) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
sendIRC().message(channel, "${event.user.nick} asked me to leave. I'll be back!")
|
sendIRC().message(channel, "${event.user.nick} asked me to leave. I'll be back!")
|
||||||
userChannelDao.getChannel(channel).send().part()
|
userChannelDao.getChannel(channel).send().part()
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Die : AbstractCommand() {
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
with(event.bot()) {
|
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.")
|
sendIRC().message(channel, "${event.user?.nick} has just signed my death sentence.")
|
||||||
stopBotReconnect()
|
stopBotReconnect()
|
||||||
sendIRC().quitServer("The Bot is Out There!")
|
sendIRC().quitServer("The Bot is Out There!")
|
||||||
|
|
|
@ -39,7 +39,7 @@ import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||||
import net.thauvin.erik.mobibot.Utils.sendList
|
import net.thauvin.erik.mobibot.Utils.sendList
|
||||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
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
|
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||||
|
|
||||||
class Ignore : AbstractCommand() {
|
class Ignore : AbstractCommand() {
|
||||||
|
@ -79,7 +79,7 @@ class Ignore : AbstractCommand() {
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
val isMe = args.trim().equals(me, true)
|
val isMe = args.trim().equals(me, true)
|
||||||
if (isMe || !isChannelOp(channel, event)) {
|
if (isMe || !event.isChannelOp(channel)) {
|
||||||
val nick = event.user.nick.lowercase()
|
val nick = event.user.nick.lowercase()
|
||||||
ignoreNick(nick, isMe, event)
|
ignoreNick(nick, isMe, event)
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,7 +88,7 @@ class Ignore : AbstractCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||||
return if (isChannelOp(channel, event)) {
|
return if (event.isChannelOp(channel)) {
|
||||||
for (h in helpOp) {
|
for (h in helpOp) {
|
||||||
event.sendMessage(helpCmdSyntax(h, event.bot().nick, true))
|
event.sendMessage(helpCmdSyntax(h, event.bot().nick, true))
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ class Ignore : AbstractCommand() {
|
||||||
override fun setProperty(key: String, value: String) {
|
override fun setProperty(key: String, value: String) {
|
||||||
super.setProperty(key, value)
|
super.setProperty(key, value)
|
||||||
if (IGNORE_PROP == key) {
|
if (IGNORE_PROP == key) {
|
||||||
ignored.addAll(value.split(LinksMgr.TAG_MATCH.toRegex()))
|
ignored.addAll(value.split(LinksManager.TAG_MATCH.toRegex()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||||
import net.thauvin.erik.mobibot.Utils.plural
|
import net.thauvin.erik.mobibot.Utils.plural
|
||||||
import net.thauvin.erik.mobibot.Utils.sendList
|
import net.thauvin.erik.mobibot.Utils.sendList
|
||||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
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.seen.Seen
|
||||||
import net.thauvin.erik.mobibot.commands.tell.Tell
|
import net.thauvin.erik.mobibot.commands.tell.Tell
|
||||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||||
|
@ -107,16 +107,16 @@ class Info(private val tell: Tell, private val seen: Seen) : AbstractCommand() {
|
||||||
info.append("Uptime: ")
|
info.append("Uptime: ")
|
||||||
.append(ManagementFactory.getRuntimeMXBean().uptime.toUptime())
|
.append(ManagementFactory.getRuntimeMXBean().uptime.toUptime())
|
||||||
.append(" [Entries: ")
|
.append(" [Entries: ")
|
||||||
.append(LinksMgr.entries.links.size)
|
.append(LinksManager.entries.links.size)
|
||||||
if (seen.isEnabled()) {
|
if (seen.isEnabled()) {
|
||||||
info.append(", Seen: ").append(seen.count())
|
info.append(", Seen: ").append(seen.count())
|
||||||
}
|
}
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
if (tell.isEnabled()) {
|
if (tell.isEnabled()) {
|
||||||
info.append(", Messages: ").append(tell.size())
|
info.append(", Messages: ").append(tell.size())
|
||||||
}
|
}
|
||||||
if (LinksMgr.twitter.isAutoPost) {
|
if (LinksManager.socialManager.entriesCount() > 0) {
|
||||||
info.append(", Twitter: ").append(LinksMgr.twitter.entriesCount())
|
info.append(", Social: ").append(LinksManager.socialManager.entriesCount())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info.append(", Recap: ").append(Recap.recaps.size).append(']')
|
info.append(", Recap: ").append(Recap.recaps.size).append(']')
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Me : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
event.bot().sendIRC().action(channel, args)
|
event.bot().sendIRC().action(channel, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Modules(private val modules: List<String>) : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
if (modules.isEmpty()) {
|
if (modules.isEmpty()) {
|
||||||
event.respondPrivateMessage("There are no enabled modules.")
|
event.respondPrivateMessage("There are no enabled modules.")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Msg : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
val msg = args.split(" ", limit = 2)
|
val msg = args.split(" ", limit = 2)
|
||||||
if (args.length > 2) {
|
if (args.length > 2) {
|
||||||
event.bot().sendIRC().message(msg[0], msg[1])
|
event.bot().sendIRC().message(msg[0], msg[1])
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Nick : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
event.bot().sendIRC().changeNick(args)
|
event.bot().sendIRC().changeNick(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Say : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
event.bot().sendIRC().message(channel, args)
|
event.bot().sendIRC().message(channel, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ class Versions : AbstractCommand() {
|
||||||
override val isVisible = true
|
override val isVisible = true
|
||||||
|
|
||||||
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
event.sendList(allVersions, 1)
|
event.sendList(allVersions, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,8 @@ class Comment : AbstractCommand() {
|
||||||
val cmds = args.substring(1).split("[.:]".toRegex(), 3)
|
val cmds = args.substring(1).split("[.:]".toRegex(), 3)
|
||||||
val entryIndex = cmds[0].toInt() - 1
|
val entryIndex = cmds[0].toInt() - 1
|
||||||
|
|
||||||
if (entryIndex < LinksMgr.entries.links.size && LinksMgr.isUpToDate(event)) {
|
if (entryIndex < LinksManager.entries.links.size && LinksManager.isUpToDate(event)) {
|
||||||
val entry: EntryLink = LinksMgr.entries.links[entryIndex]
|
val entry: EntryLink = LinksManager.entries.links[entryIndex]
|
||||||
val commentIndex = cmds[1].toInt() - 1
|
val commentIndex = cmds[1].toInt() - 1
|
||||||
if (commentIndex < entry.comments.size) {
|
if (commentIndex < entry.comments.size) {
|
||||||
when (val cmd = cmds[2].trim()) {
|
when (val cmd = cmds[2].trim()) {
|
||||||
|
@ -87,7 +87,7 @@ class Comment : AbstractCommand() {
|
||||||
|
|
||||||
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
override fun helpResponse(channel: String, topic: String, event: GenericMessageEvent): Boolean {
|
||||||
if (super.helpResponse(channel, topic, event)) {
|
if (super.helpResponse(channel, topic, event)) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
event.sendMessage("To change a comment's author:")
|
event.sendMessage("To change a comment's author:")
|
||||||
event.sendMessage(helpFormat("${Constants.LINK_CMD}1.1:?<nick>"))
|
event.sendMessage(helpFormat("${Constants.LINK_CMD}1.1:?<nick>"))
|
||||||
}
|
}
|
||||||
|
@ -108,11 +108,11 @@ class Comment : AbstractCommand() {
|
||||||
commentIndex: Int,
|
commentIndex: Int,
|
||||||
event: GenericMessageEvent
|
event: GenericMessageEvent
|
||||||
) {
|
) {
|
||||||
if (isChannelOp(channel, event) && cmd.length > 1) {
|
if (event.isChannelOp(channel) && cmd.length > 1) {
|
||||||
val comment = entry.getComment(commentIndex)
|
val comment = entry.getComment(commentIndex)
|
||||||
comment.nick = cmd.substring(1)
|
comment.nick = cmd.substring(1)
|
||||||
event.sendMessage(buildComment(entryIndex, commentIndex, comment))
|
event.sendMessage(buildComment(entryIndex, commentIndex, comment))
|
||||||
LinksMgr.entries.save()
|
LinksManager.entries.save()
|
||||||
} else {
|
} else {
|
||||||
event.sendMessage("Please ask a channel op to change the author of this comment for you.")
|
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,
|
commentIndex: Int,
|
||||||
event: GenericMessageEvent
|
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)
|
entry.deleteComment(commentIndex)
|
||||||
event.sendMessage("Comment ${entryIndex.toLinkLabel()}.${commentIndex + 1} removed.")
|
event.sendMessage("Comment ${entryIndex.toLinkLabel()}.${commentIndex + 1} removed.")
|
||||||
LinksMgr.entries.save()
|
LinksManager.entries.save()
|
||||||
} else {
|
} else {
|
||||||
event.sendMessage("Please ask a channel op to delete this comment for you.")
|
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)
|
entry.setComment(commentIndex, cmd, event.user.nick)
|
||||||
event.sendMessage(buildComment(entryIndex, commentIndex, entry.getComment(commentIndex)))
|
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) {
|
private fun showComment(entry: EntryLink, entryIndex: Int, commentIndex: Int, event: GenericMessageEvent) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* LinksMgr.kt
|
* LinksManager.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* 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.buildLink
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
||||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
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.jsoup.Jsoup
|
||||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
import org.pircbotx.hooks.types.GenericMessageEvent
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class LinksMgr : AbstractCommand() {
|
class LinksManager : AbstractCommand() {
|
||||||
private val defaultTags: MutableList<String> = mutableListOf()
|
private val defaultTags: MutableList<String> = mutableListOf()
|
||||||
private val keywords: MutableList<String> = mutableListOf()
|
private val keywords: MutableList<String> = mutableListOf()
|
||||||
|
|
||||||
|
@ -83,10 +83,10 @@ class LinksMgr : AbstractCommand() {
|
||||||
val pinboard = Pinboard()
|
val pinboard = Pinboard()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Twitter handler.
|
* Social Manager handler.
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@JvmField
|
||||||
val twitter = Twitter()
|
val socialManager = SocialManager()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let the user know if the entries are too old to be modified.
|
* 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)
|
pinboard.addPin(event.bot().serverHostname, entry)
|
||||||
|
|
||||||
// Queue link for posting to Twitter.
|
// Queue link for posting to social media.
|
||||||
twitter.queueEntry(index)
|
socialManager.queueEntry(index)
|
||||||
|
|
||||||
entries.save()
|
entries.save()
|
||||||
|
|
|
@ -39,7 +39,7 @@ import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||||
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
import net.thauvin.erik.mobibot.Utils.isChannelOp
|
||||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
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
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
||||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||||
|
@ -71,7 +71,7 @@ class Posting : AbstractCommand() {
|
||||||
val cmd = cmds[1].trim()
|
val cmd = cmds[1].trim()
|
||||||
if (cmd.isBlank()) {
|
if (cmd.isBlank()) {
|
||||||
showEntry(entryIndex, event) // L1:
|
showEntry(entryIndex, event) // L1:
|
||||||
} else if (LinksMgr.isUpToDate(event)) {
|
} else if (LinksManager.isUpToDate(event)) {
|
||||||
if (cmd == "-") {
|
if (cmd == "-") {
|
||||||
removeEntry(channel, entryIndex, event) // L1:-
|
removeEntry(channel, entryIndex, event) // L1:-
|
||||||
} else {
|
} else {
|
||||||
|
@ -102,7 +102,7 @@ class Posting : AbstractCommand() {
|
||||||
if (cmd.length > 1) {
|
if (cmd.length > 1) {
|
||||||
val entry: EntryLink = entries.links[entryIndex]
|
val entry: EntryLink = entries.links[entryIndex]
|
||||||
entry.title = cmd.substring(1).trim()
|
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))
|
event.sendMessage(EntriesUtils.buildLink(entryIndex, entry))
|
||||||
entries.save()
|
entries.save()
|
||||||
}
|
}
|
||||||
|
@ -110,12 +110,12 @@ class Posting : AbstractCommand() {
|
||||||
|
|
||||||
private fun changeUrl(channel: String, cmd: String, entryIndex: Int, event: GenericMessageEvent) {
|
private fun changeUrl(channel: String, cmd: String, entryIndex: Int, event: GenericMessageEvent) {
|
||||||
val entry: EntryLink = entries.links[entryIndex]
|
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)
|
val link = cmd.substring(1)
|
||||||
if (link.matches(LinksMgr.LINK_MATCH.toRegex())) {
|
if (link.matches(LinksManager.LINK_MATCH.toRegex())) {
|
||||||
val oldLink = entry.link
|
val oldLink = entry.link
|
||||||
entry.link = 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))
|
event.sendMessage(EntriesUtils.buildLink(entryIndex, entry))
|
||||||
entries.save()
|
entries.save()
|
||||||
}
|
}
|
||||||
|
@ -123,11 +123,11 @@ class Posting : AbstractCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeAuthor(channel: String, cmd: String, index: Int, event: GenericMessageEvent) {
|
private fun changeAuthor(channel: String, cmd: String, index: Int, event: GenericMessageEvent) {
|
||||||
if (isChannelOp(channel, event)) {
|
if (event.isChannelOp(channel)) {
|
||||||
if (cmd.length > 1) {
|
if (cmd.length > 1) {
|
||||||
val entry: EntryLink = entries.links[index]
|
val entry: EntryLink = entries.links[index]
|
||||||
entry.nick = cmd.substring(1)
|
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))
|
event.sendMessage(EntriesUtils.buildLink(index, entry))
|
||||||
entries.save()
|
entries.save()
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,9 @@ class Posting : AbstractCommand() {
|
||||||
|
|
||||||
private fun removeEntry(channel: String, index: Int, event: GenericMessageEvent) {
|
private fun removeEntry(channel: String, index: Int, event: GenericMessageEvent) {
|
||||||
val entry: EntryLink = entries.links[index]
|
val entry: EntryLink = entries.links[index]
|
||||||
if (entry.login == event.user.login || isChannelOp(channel, event)) {
|
if (entry.login == event.user.login || event.isChannelOp(channel)) {
|
||||||
LinksMgr.pinboard.deletePin(entry)
|
LinksManager.pinboard.deletePin(entry)
|
||||||
LinksMgr.twitter.removeEntry(index)
|
LinksManager.socialManager.removeEntry(index)
|
||||||
entries.links.removeAt(index)
|
entries.links.removeAt(index)
|
||||||
event.sendMessage("Entry ${index.toLinkLabel()} removed.")
|
event.sendMessage("Entry ${index.toLinkLabel()} removed.")
|
||||||
entries.save()
|
entries.save()
|
||||||
|
|
|
@ -60,15 +60,15 @@ class Tags : AbstractCommand() {
|
||||||
val cmds = args.substring(1).split("${Constants.TAG_CMD}:", limit = 2)
|
val cmds = args.substring(1).split("${Constants.TAG_CMD}:", limit = 2)
|
||||||
val index = cmds[0].toInt() - 1
|
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 cmd = cmds[1].trim()
|
||||||
val entry: EntryLink = LinksMgr.entries.links[index]
|
val entry: EntryLink = LinksManager.entries.links[index]
|
||||||
if (cmd.isNotEmpty()) {
|
if (cmd.isNotEmpty()) {
|
||||||
if (entry.login == event.user.login || isChannelOp(channel, event)) {
|
if (entry.login == event.user.login || event.isChannelOp(channel)) {
|
||||||
entry.setTags(cmd)
|
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))
|
event.sendMessage(EntriesUtils.buildTags(index, entry))
|
||||||
LinksMgr.entries.save()
|
LinksManager.entries.save()
|
||||||
} else {
|
} else {
|
||||||
event.sendMessage("Please ask a channel op to change the tags for you.")
|
event.sendMessage("Please ask a channel op to change the tags for you.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import net.thauvin.erik.mobibot.Utils.helpFormat
|
||||||
import net.thauvin.erik.mobibot.Utils.lastOrEmpty
|
import net.thauvin.erik.mobibot.Utils.lastOrEmpty
|
||||||
import net.thauvin.erik.mobibot.Utils.sendMessage
|
import net.thauvin.erik.mobibot.Utils.sendMessage
|
||||||
import net.thauvin.erik.mobibot.commands.AbstractCommand
|
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
|
||||||
import net.thauvin.erik.mobibot.entries.EntryLink
|
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||||
import org.pircbotx.hooks.events.PrivateMessageEvent
|
import org.pircbotx.hooks.events.PrivateMessageEvent
|
||||||
|
|
|
@ -81,7 +81,7 @@ class Tell(private val serialObject: String) : AbstractCommand() {
|
||||||
* Cleans the messages queue.
|
* Cleans the messages queue.
|
||||||
*/
|
*/
|
||||||
private fun clean(): Boolean {
|
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) {
|
override fun commandResponse(channel: String, args: String, event: GenericMessageEvent) {
|
||||||
|
@ -89,7 +89,7 @@ class Tell(private val serialObject: String) : AbstractCommand() {
|
||||||
if (args.isBlank()) {
|
if (args.isBlank()) {
|
||||||
helpResponse(channel, args, event)
|
helpResponse(channel, args, event)
|
||||||
} else if (args.startsWith(View.VIEW_CMD)) {
|
} 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)
|
viewAll(event)
|
||||||
} else {
|
} else {
|
||||||
viewMessages(event)
|
viewMessages(event)
|
||||||
|
@ -120,7 +120,7 @@ class Tell(private val serialObject: String) : AbstractCommand() {
|
||||||
} else {
|
} else {
|
||||||
if (messages.removeIf {
|
if (messages.removeIf {
|
||||||
it.id == id &&
|
it.id == id &&
|
||||||
(it.sender.equals(event.user.nick, true) || isChannelOp(channel, event))
|
(it.sender.equals(event.user.nick, true) || event.isChannelOp(channel))
|
||||||
}) {
|
}) {
|
||||||
save()
|
save()
|
||||||
event.sendMessage("The message was deleted from the queue.")
|
event.sendMessage("The message was deleted from the queue.")
|
||||||
|
@ -167,7 +167,7 @@ class Tell(private val serialObject: String) : AbstractCommand() {
|
||||||
* Saves the messages queue.
|
* Saves the messages queue.
|
||||||
*/
|
*/
|
||||||
private fun save() {
|
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)
|
initProperties(MAX_DAYS_PROP, MAX_SIZE_PROP)
|
||||||
|
|
||||||
// Load the message queue
|
// Load the message queue
|
||||||
messages.addAll(TellMessagesMgr.load(serialObject))
|
messages.addAll(TellManager.load(serialObject))
|
||||||
if (clean()) {
|
if (clean()) {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TellMessagesMgr.kt
|
* TellManager.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
|
@ -41,8 +41,8 @@ import java.time.LocalDateTime
|
||||||
/**
|
/**
|
||||||
* The Tell Messages Manager.
|
* The Tell Messages Manager.
|
||||||
*/
|
*/
|
||||||
object TellMessagesMgr {
|
object TellManager {
|
||||||
private val logger: Logger = LoggerFactory.getLogger(TellMessagesMgr::class.java)
|
private val logger: Logger = LoggerFactory.getLogger(TellManager::class.java)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans the messages queue.
|
* Cleans the messages queue.
|
|
@ -45,11 +45,11 @@ class Entries(
|
||||||
var lastPubDate = today()
|
var lastPubDate = today()
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
lastPubDate = FeedsMgr.loadFeed(this)
|
lastPubDate = FeedsManager.loadFeed(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
lastPubDate = today()
|
lastPubDate = today()
|
||||||
FeedsMgr.saveFeed(this)
|
FeedsManager.saveFeed(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ package net.thauvin.erik.mobibot.entries
|
||||||
|
|
||||||
import com.rometools.rome.feed.synd.SyndCategory
|
import com.rometools.rome.feed.synd.SyndCategory
|
||||||
import com.rometools.rome.feed.synd.SyndCategoryImpl
|
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.io.Serializable
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -169,7 +169,7 @@ class EntryLink(
|
||||||
* Sets the tags.
|
* Sets the tags.
|
||||||
*/
|
*/
|
||||||
fun setTags(tags: String) {
|
fun setTags(tags: String) {
|
||||||
setTags(tags.split(LinksMgr.TAG_MATCH))
|
setTags(tags.split(LinksManager.TAG_MATCH))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* FeedsMgr.kt
|
* FeedsManager.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
|
@ -55,9 +55,9 @@ import kotlin.io.path.exists
|
||||||
/**
|
/**
|
||||||
* Manages the RSS feeds.
|
* Manages the RSS feeds.
|
||||||
*/
|
*/
|
||||||
class FeedsMgr private constructor() {
|
class FeedsManager private constructor() {
|
||||||
companion object {
|
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.
|
// The file containing the current entries.
|
||||||
private const val currentXml = "current.xml"
|
private const val currentXml = "current.xml"
|
150
src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt
Normal file
150
src/main/kotlin/net/thauvin/erik/mobibot/modules/Mastodon.kt
Normal file
|
@ -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 <message>"))
|
||||||
|
properties[AUTO_POST_PROP] = "false"
|
||||||
|
initProperties(ACCESS_TOKEN_PROP, HANDLE_PROP, INSTANCE_PROP)
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,57 +31,27 @@
|
||||||
*/
|
*/
|
||||||
package net.thauvin.erik.mobibot.modules
|
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.Utils.helpFormat
|
||||||
import net.thauvin.erik.mobibot.commands.links.LinksMgr
|
import net.thauvin.erik.mobibot.entries.EntryLink
|
||||||
import net.thauvin.erik.mobibot.entries.EntriesUtils.toLinkLabel
|
import net.thauvin.erik.mobibot.social.SocialModule
|
||||||
import org.pircbotx.hooks.types.GenericMessageEvent
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import twitter4j.TwitterException
|
import twitter4j.TwitterException
|
||||||
import java.util.Timer
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Twitter module.
|
* The Twitter module.
|
||||||
*/
|
*/
|
||||||
class Twitter : ThreadedModule() {
|
class Twitter : SocialModule() {
|
||||||
private val logger: Logger = LoggerFactory.getLogger(Twitter::class.java)
|
|
||||||
|
|
||||||
private val timer = Timer(true)
|
|
||||||
|
|
||||||
// Twitter auto-posts.
|
|
||||||
private val entries: MutableSet<Int> = HashSet()
|
|
||||||
|
|
||||||
override val name = "Twitter"
|
override val name = "Twitter"
|
||||||
|
|
||||||
/**
|
override val handle: String?
|
||||||
* Add an entry to be posted on Twitter.
|
|
||||||
*/
|
|
||||||
private fun addEntry(index: Int) {
|
|
||||||
entries.add(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun entriesCount(): Int {
|
|
||||||
return entries.size
|
|
||||||
}
|
|
||||||
|
|
||||||
private val handle: String?
|
|
||||||
get() = properties[HANDLE_PROP]
|
get() = properties[HANDLE_PROP]
|
||||||
|
|
||||||
private fun hasEntry(index: Int): Boolean {
|
override val isAutoPost: Boolean
|
||||||
return entries.contains(index)
|
get() = isEnabled && properties[AUTO_POST_PROP].toBoolean()
|
||||||
}
|
|
||||||
|
|
||||||
val isAutoPost: Boolean
|
|
||||||
get() = isEnabled && properties[AUTOPOST_PROP].toBoolean()
|
|
||||||
|
|
||||||
override val isValidProperties: Boolean
|
override val isValidProperties: Boolean
|
||||||
get() {
|
get() {
|
||||||
for (s in propertyKeys) {
|
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
|
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) {
|
override fun formatEntry(entry: EntryLink): String {
|
||||||
if (isEnabled && !handle.isNullOrBlank()) {
|
return "${entry.title} ${entry.link} via ${entry.nick} on ${entry.channel}"
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts on Twitter.
|
* Posts on Twitter.
|
||||||
*/
|
*/
|
||||||
@Throws(ModuleException::class)
|
@Throws(ModuleException::class)
|
||||||
fun post(handle: String = "${properties[HANDLE_PROP]}", message: String, isDm: Boolean): String {
|
override fun post(message: String, isDm: Boolean): String {
|
||||||
return twitterPost(
|
return tweet(
|
||||||
properties[CONSUMER_KEY_PROP],
|
consumerKey = properties[CONSUMER_KEY_PROP],
|
||||||
properties[CONSUMER_SECRET_PROP],
|
consumerSecret = properties[CONSUMER_SECRET_PROP],
|
||||||
properties[TOKEN_PROP],
|
token = properties[TOKEN_PROP],
|
||||||
properties[TOKEN_SECRET_PROP],
|
tokenSecret = properties[TOKEN_SECRET_PROP],
|
||||||
handle,
|
handle = handle,
|
||||||
message,
|
message = message,
|
||||||
isDm
|
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 {
|
companion object {
|
||||||
// Property keys
|
// 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_KEY_PROP = "twitter-consumerKey"
|
||||||
const val CONSUMER_SECRET_PROP = "twitter-consumerSecret"
|
const val CONSUMER_SECRET_PROP = "twitter-consumerSecret"
|
||||||
const val HANDLE_PROP = "twitter-handle"
|
const val HANDLE_PROP = "twitter-handle"
|
||||||
|
@ -198,11 +94,11 @@ class Twitter : ThreadedModule() {
|
||||||
private const val TWITTER_CMD = "twitter"
|
private const val TWITTER_CMD = "twitter"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts on Twitter.
|
* Tweets on Twitter.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(ModuleException::class)
|
@Throws(ModuleException::class)
|
||||||
fun twitterPost(
|
fun tweet(
|
||||||
consumerKey: String?,
|
consumerKey: String?,
|
||||||
consumerSecret: String?,
|
consumerSecret: String?,
|
||||||
token: String?,
|
token: String?,
|
||||||
|
@ -236,7 +132,7 @@ class Twitter : ThreadedModule() {
|
||||||
commands.add(TWITTER_CMD)
|
commands.add(TWITTER_CMD)
|
||||||
help.add("To post to Twitter:")
|
help.add("To post to Twitter:")
|
||||||
help.add(helpFormat("%c $TWITTER_CMD <message>"))
|
help.add(helpFormat("%c $TWITTER_CMD <message>"))
|
||||||
properties[AUTOPOST_PROP] = "false"
|
properties[AUTO_POST_PROP] = "false"
|
||||||
initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP)
|
initProperties(CONSUMER_KEY_PROP, CONSUMER_SECRET_PROP, HANDLE_PROP, TOKEN_PROP, TOKEN_SECRET_PROP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
117
src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt
Normal file
117
src/main/kotlin/net/thauvin/erik/mobibot/social/SocialManager.kt
Normal file
|
@ -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<Int> = HashSet()
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger(SocialManager::class.java)
|
||||||
|
private val modules = ArrayList<SocialModule>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt
Normal file
107
src/main/kotlin/net/thauvin/erik/mobibot/social/SocialModule.kt
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* TwitterTimer.kt
|
* SocialTimer.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2004-2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
|
@ -30,13 +30,12 @@
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* 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
|
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() {
|
override fun run() {
|
||||||
twitter.postEntry(index)
|
socialManager.postEntry(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,6 +33,8 @@ package net.thauvin.erik.mobibot
|
||||||
|
|
||||||
import org.testng.annotations.BeforeSuite
|
import org.testng.annotations.BeforeSuite
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.UnknownHostException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
@ -56,6 +58,15 @@ open class LocalProperties {
|
||||||
companion object {
|
companion object {
|
||||||
private val localProps = Properties()
|
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 {
|
fun getProperty(key: String): String {
|
||||||
return if (localProps.containsKey(key)) {
|
return if (localProps.containsKey(key)) {
|
||||||
localProps.getProperty(key)
|
localProps.getProperty(key)
|
||||||
|
|
|
@ -41,31 +41,34 @@ import assertk.assertions.size
|
||||||
import net.thauvin.erik.mobibot.Constants
|
import net.thauvin.erik.mobibot.Constants
|
||||||
import org.testng.annotations.Test
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
class LinksMgrTest {
|
class LinksManagerTest {
|
||||||
private val linksMgr = LinksMgr()
|
private val linksManager = LinksManager()
|
||||||
|
|
||||||
@Test(groups = ["commands", "links"])
|
@Test(groups = ["commands", "links"])
|
||||||
fun fetchTitle() {
|
fun fetchTitle() {
|
||||||
assertThat(linksMgr.fetchTitle("https://erik.thauvin.net/"), "fetchTitle(Erik)").contains("Erik's Weblog")
|
assertThat(linksManager.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://www.google.com/foo"),
|
||||||
|
"fetchTitle(Foo)"
|
||||||
|
).isEqualTo(Constants.NO_TITLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(groups = ["commands", "links"])
|
@Test(groups = ["commands", "links"])
|
||||||
fun testMatches() {
|
fun testMatches() {
|
||||||
assertThat(linksMgr.matches("https://www.example.com/"), "matches(url)").isTrue()
|
assertThat(linksManager.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("HTTP://erik.thauvin.net/blog/ Erik's Weblog"), "matches(HTTP)").isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(groups = ["commands", "links"])
|
@Test(groups = ["commands", "links"])
|
||||||
fun matchTagKeywordsTest() {
|
fun matchTagKeywordsTest() {
|
||||||
linksMgr.setProperty(LinksMgr.KEYWORDS_PROP, "key1 key2,key3")
|
linksManager.setProperty(LinksManager.KEYWORDS_PROP, "key1 key2,key3")
|
||||||
val tags = mutableListOf<String>()
|
val tags = mutableListOf<String>()
|
||||||
|
|
||||||
linksMgr.matchTagKeywords("Test title with key2", tags)
|
linksManager.matchTagKeywords("Test title with key2", tags)
|
||||||
assertThat(tags, "tags").contains("key2")
|
assertThat(tags, "tags").contains("key2")
|
||||||
tags.clear()
|
tags.clear()
|
||||||
|
|
||||||
linksMgr.matchTagKeywords("Test key3 title with key1", tags)
|
linksManager.matchTagKeywords("Test key3 title with key1", tags)
|
||||||
assertThat(tags, "tags(key1, key3)").all {
|
assertThat(tags, "tags(key1, key3)").all {
|
||||||
contains("key1")
|
contains("key1")
|
||||||
contains("key3")
|
contains("key3")
|
|
@ -45,7 +45,7 @@ class ViewTest {
|
||||||
val view = View()
|
val view = View()
|
||||||
|
|
||||||
for (i in 1..10) {
|
for (i in 1..10) {
|
||||||
LinksMgr.entries.links.add(
|
LinksManager.entries.links.add(
|
||||||
EntryLink(
|
EntryLink(
|
||||||
"https://www.example.com/$i",
|
"https://www.example.com/$i",
|
||||||
"Example $i",
|
"Example $i",
|
||||||
|
@ -98,11 +98,11 @@ class ViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(view.parseArgs(""), "parseArgs()").all {
|
assertThat(view.parseArgs(""), "parseArgs()").all {
|
||||||
prop(Pair<Int, String>::first).isEqualTo(LinksMgr.entries.links.size - View.MAX_ENTRIES)
|
prop(Pair<Int, String>::first).isEqualTo(LinksManager.entries.links.size - View.MAX_ENTRIES)
|
||||||
prop(Pair<Int, String>::second).isEqualTo("")
|
prop(Pair<Int, String>::second).isEqualTo("")
|
||||||
}
|
}
|
||||||
|
|
||||||
LinksMgr.entries.links.clear()
|
LinksManager.entries.links.clear()
|
||||||
|
|
||||||
assertThat(view.parseArgs("4"), "parseArgs(4)").all {
|
assertThat(view.parseArgs("4"), "parseArgs(4)").all {
|
||||||
prop(Pair<Int, String>::first).isEqualTo(0)
|
prop(Pair<Int, String>::first).isEqualTo(0)
|
||||||
|
|
|
@ -60,7 +60,7 @@ class TellMessagesMgrTest {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
fun saveTest() {
|
fun saveTest() {
|
||||||
TellMessagesMgr.save(testFile.toAbsolutePath().toString(), testMessages)
|
TellManager.save(testFile.toAbsolutePath().toString(), testMessages)
|
||||||
assertThat(testFile.fileSize()).isGreaterThan(0)
|
assertThat(testFile.fileSize()).isGreaterThan(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +75,14 @@ class TellMessagesMgrTest {
|
||||||
queued = LocalDateTime.now().minusDays(maxDays)
|
queued = LocalDateTime.now().minusDays(maxDays)
|
||||||
})
|
})
|
||||||
val size = testMessages.size
|
val size = testMessages.size
|
||||||
assertThat(TellMessagesMgr.clean(testMessages, maxDays + 2), "clean(maxDays=${maxDays + 2})").isFalse()
|
assertThat(TellManager.clean(testMessages, maxDays + 2), "clean(maxDays=${maxDays + 2})").isFalse()
|
||||||
assertThat(TellMessagesMgr.clean(testMessages, maxDays), "clean(maxDays=$maxDays)").isTrue()
|
assertThat(TellManager.clean(testMessages, maxDays), "clean(maxDays=$maxDays)").isTrue()
|
||||||
assertThat(testMessages, "testMessages").size().isEqualTo(size - 1)
|
assertThat(testMessages, "testMessages").size().isEqualTo(size - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(groups = ["commands", "tell"])
|
@Test(groups = ["commands", "tell"])
|
||||||
fun loadTest() {
|
fun loadTest() {
|
||||||
val messages = TellMessagesMgr.load(testFile.toAbsolutePath().toString())
|
val messages = TellManager.load(testFile.toAbsolutePath().toString())
|
||||||
for (i in messages.indices) {
|
for (i in messages.indices) {
|
||||||
assertThat(messages).index(i).all {
|
assertThat(messages).index(i).all {
|
||||||
prop(TellMessage::sender).isEqualTo(testMessages[i].sender)
|
prop(TellMessage::sender).isEqualTo(testMessages[i].sender)
|
||||||
|
|
|
@ -65,7 +65,7 @@ class FeedMgrTest {
|
||||||
@Test(groups = ["entries"])
|
@Test(groups = ["entries"])
|
||||||
fun testFeedMgr() {
|
fun testFeedMgr() {
|
||||||
// Load the feed
|
// 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)
|
assertThat(entries.links, "entries.links").size().isEqualTo(2)
|
||||||
entries.links.forEachIndexed { i, entryLink ->
|
entries.links.forEachIndexed { i, entryLink ->
|
||||||
|
@ -101,7 +101,7 @@ class FeedMgrTest {
|
||||||
val backlogFile = Paths.get("${entries.logsDir}${today()}.xml")
|
val backlogFile = Paths.get("${entries.logsDir}${today()}.xml")
|
||||||
|
|
||||||
// Save the feed
|
// Save the feed
|
||||||
FeedsMgr.saveFeed(entries, currentFile.name)
|
FeedsManager.saveFeed(entries, currentFile.name)
|
||||||
|
|
||||||
assertThat(currentFile, "currentFile").exists()
|
assertThat(currentFile, "currentFile").exists()
|
||||||
assertThat(backlogFile, "backlogFile").exists()
|
assertThat(backlogFile, "backlogFile").exists()
|
||||||
|
@ -110,7 +110,7 @@ class FeedMgrTest {
|
||||||
|
|
||||||
// Load the test feed
|
// Load the test feed
|
||||||
entries.links.clear()
|
entries.links.clear()
|
||||||
FeedsMgr.loadFeed(entries, currentFile.name)
|
FeedsManager.loadFeed(entries, currentFile.name)
|
||||||
|
|
||||||
entries.links.forEachIndexed { i, entryLink ->
|
entries.links.forEachIndexed { i, entryLink ->
|
||||||
assertThat(entryLink.title, "entryLink.title[${i + 1}]").isEqualTo("Example ${i + 1}")
|
assertThat(entryLink.title, "entryLink.title[${i + 1}]").isEqualTo("Example ${i + 1}")
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,31 +35,19 @@ import assertk.assertThat
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import assertk.assertions.isSuccess
|
import assertk.assertions.isSuccess
|
||||||
import net.thauvin.erik.mobibot.LocalProperties
|
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 org.testng.annotations.Test
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `TwitterTest` class.
|
* The `TwitterTest` class.
|
||||||
*/
|
*/
|
||||||
class TwitterTest : LocalProperties() {
|
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"])
|
@Test(groups = ["modules"])
|
||||||
@Throws(ModuleException::class)
|
@Throws(ModuleException::class)
|
||||||
fun testPostTwitter() {
|
fun testTweet() {
|
||||||
val msg = "Testing Twitter API from $ci"
|
val msg = "Testing Twitter API from ${getHostName()}"
|
||||||
assertThat {
|
assertThat {
|
||||||
twitterPost(
|
tweet(
|
||||||
getProperty(Twitter.CONSUMER_KEY_PROP),
|
getProperty(Twitter.CONSUMER_KEY_PROP),
|
||||||
getProperty(Twitter.CONSUMER_SECRET_PROP),
|
getProperty(Twitter.CONSUMER_SECRET_PROP),
|
||||||
getProperty(Twitter.TOKEN_PROP),
|
getProperty(Twitter.TOKEN_PROP),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#Generated by the Semver Plugin for Gradle
|
#Generated by the Semver Plugin for Gradle
|
||||||
#Sun Dec 04 17:34:43 PST 2022
|
#Mon Dec 05 21:57:24 PST 2022
|
||||||
version.buildmeta=799
|
version.buildmeta=814
|
||||||
version.major=0
|
version.major=0
|
||||||
version.minor=8
|
version.minor=8
|
||||||
version.patch=0
|
version.patch=0
|
||||||
version.prerelease=rc
|
version.prerelease=rc
|
||||||
version.project=mobibot
|
version.project=mobibot
|
||||||
version.semver=0.8.0-rc+799
|
version.semver=0.8.0-rc+814
|
||||||
|
|
|
@ -81,9 +81,9 @@
|
||||||
<li>Performing Google searches
|
<li>Performing Google searches
|
||||||
<div><code>mobibot: google mobitopia on irc</code></div>
|
<div><code>mobibot: google mobitopia on irc</code></div>
|
||||||
</li>
|
</li>
|
||||||
<li>Getting answers from <a href="https://wolframalpha.com/">Wolfram Alpha</a>
|
<li>Getting answers from <a href="https://wolframalpha.com/">Wolfram Alpha</a> and <a href="https://chat.openai.com/chat">ChatGPT</a>
|
||||||
<div><code>mobibot: wolfram days until christmas</code></div>
|
<div><code>mobibot: wolfram days until christmas</code></div>
|
||||||
<div><code>mobibot: wolfram 1 gallon to liter</code></div>
|
<div><code>mobibot: chatgpt explain quantum computing in simple terms</code></div>
|
||||||
</li>
|
</li>
|
||||||
<li>Displaying weather information
|
<li>Displaying weather information
|
||||||
<div><code>mobibot: weather san francisco</code></div>
|
<div><code>mobibot: weather san francisco</code></div>
|
||||||
|
@ -116,13 +116,13 @@
|
||||||
<li>Random jokes from <a href="https://v2.jokeapi.dev/">Sv443's JokeAPI</a>
|
<li>Random jokes from <a href="https://v2.jokeapi.dev/">Sv443's JokeAPI</a>
|
||||||
<div><code>mobibot: joke</code></div>
|
<div><code>mobibot: joke</code></div>
|
||||||
</li>
|
</li>
|
||||||
<li>Rolling dice or Playing war and rock paper scissors
|
<li>Playing dice, war or rock paper scissors
|
||||||
<div><code>mobibot: dice</code></div>
|
<div><code>mobibot: dice</code></div>
|
||||||
<div><code>mobibot: war</code></div>
|
<div><code>mobibot: war</code></div>
|
||||||
<div><code>mobibot: paper</code></div>
|
<div><code>mobibot: paper</code></div>
|
||||||
<div><code>mobibot: rock</code></div>
|
<div><code>mobibot: rock</code></div>
|
||||||
</li>
|
</li>
|
||||||
<li>Posting to <a href="https://twitter.com/mobitopia">Twitter</a></li>
|
<li>Posting to <a href="https://twitter.com/mobitopia">Twitter</a> and <a href="https://joinmastodon.org/">Mastodon</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Some of the internal features include RSS feed backlogs, rolling logs, debugging toggle and much more.</p>
|
<p>Some of the internal features include RSS feed backlogs, rolling logs, debugging toggle and much more.</p>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue