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